Here’s a neat little practice — splitting applications into pieces for “interface” and “engine”.
Once split up that way, it’s easy to shuffle classes around, because the “interface” classes are anything a remote client would need to see, and the “engine” are the pieces we can safely hide from the world. You can put them in an interface.jar, and distribute that, and an engine.jar, and deploy that.
That works really well, but there are a couple of surprises.
Not surprisingly, the basic classes in the interface package are interfaces. They simple define the boundary of the logic that lives in the engine. If my service is called the Wooga service, then I’ll have an interface class called Wooga.java that defines the methods my service exposes. There might be more than one, when I want to split my service into smaller pieces.
The first surprise, the data model classes go into the “interface” package. At first that seems odd, because it seems like the data model is an internal, hidden thing. But think about it: if I write a client and expose my data objects to that client, why should I have an “internal” package for data objects, and an identical “external” package?
No, data objects are part of the interface. If I expose a method called “calculatePayment” in the interface package, then it returns a PaymentSchedule object, then of course the returned object’s class is going to have to be in the interface too.
What it boils down to is that the defined interface for the application includes the data model classes that are shared with the client.
Sometimes there are internal data model artifacts — maybe related to authorization, accounting or monitoring — that of course don’t show up in the interface. But again, that’s fair. They go in the “engine” where they can safely be hidden from any consumer of my application.
Another surprising consequence of splitting interface and engine is that a remote client to the service and internal implementations of the service can implement the same interface. Which means they are transparently pluggable. That has a lot of power.
If you don’t want to pay the price of a network hop and additional infrastructure — you don’t want to run a separate box — just drop the engine.jar locally with your application and invoke the engine directly through the defined interface.
If you want to split it out later, just remove the engine.jar and leave interface.jar, and configure your application to use the remote client instead of the engine. You won’t have to make any code changes to your application. It has no idea the engine is now remote, except that everything takes a little longer.
I usually also throw my client implementation into the interface package as well, providing for serialization and deserialization of parameter objects and response objects. A client isn’t limited to this implementation, but in most cases they’re not interested in writing their own anyway.
Part of the motivation for this came from the idea of a “Hexagonal Architecture”, which is a response to the old “N-Tiered Architecture”. The idea is that a software component has a hidden internal implementation, which is accessible through a variety of interfaces — not just one as the N-Tiered architecture usually depicts.
The hexagonal component will also talk out to other components through their “interface” packages. This makes for a composable system, where pieces can be co-located, distributed, tiered, load-balanced, etc. in a very flexible and mostly transparent way.
More about the Hexagonal Architecture: