-
Notifications
You must be signed in to change notification settings - Fork 0
Separate APIs
Model entities have two separate APIs:
- a public API intended for most clients.
- a private API intended only for distinguished clients.
Public APIs are defined by interfaces. Private APIs are defined by nested implementations of the interfaces.
Nesting helps us avoid class explosion and unwieldy names. It is also intention-revealing. And since we nest inside succinct interfaces, readability is not impaired.
For example, Codelist and Codelist.Private define the public and private APIs of codelists:
public interface Codelist ... {
...
class Private ... implements Codelist {
...
}
}
Distinguished clients explicitly "reveal" private APIs with static facilities of the Data DSL, e.g.:
Codelist list;
...
Codelist.Private plist = Data.reveal(list);
##What's with the separation?
The rationale for this separation is nicely illustrated in relation to reified updates and their quintessentially object-oriented API. As a remainder:
Codelist list,changeset;
...
list.update(changeset);
We invoke update() on a codelist and the invocation propagates recursively along containment and inheritance hierarchies with all the elegance and modular goodness of dynamic dispatch.
If it's about implementing logic over entities there's hardly a better place than entities themselves. Updates are just one case in point.
When it comes to consuming logic, however, things are different. Indeed, we don't expose update() to all clients. We route most of them to Repositories instead:
CodelistRepository repo;
Codelist changeset;
...
repo.update(changeset);
Repositories are in fact some the few clients that invoke update() on entities. There are a few reasons for this:
-
we need to update only persistent data. Going through Repositories makes this fact quite clear. In general, we like to follow a consistent CRUD narrative whereby entities are added to Repositories, updated in Repositories, and deleted from Repositories.
-
we go through Repositories for batch updates anyway. It seems consistent to go there for fine-grained updates too.
-
we want control over updates and we get that if they pass through a single point. For example, we want to fire events before and after updates, like we do for other CRUD operations. If changesets have been already consumed by then, we have nothing to put in the events.
-
similarly, we want latitude when it comes to persisting changes. If ever required, for example, we may bypass the model altogether and "apply" the changeset directly against the store. Going through Repositories fits well into our quest for persistence ignorance.
-
update()is not the only method of the API. For example, we need astatus()method to distinguish real entities from changesets, and then again changesets that model modifications from changesets that models deletions.status()is clearly implementation-facing and would pollute the client's view of the entity.
These observations generalise: for clarity, cleanliness, consistency, flexibility, and control we want to rally most clients around service APIs; for convenience, however, we want to implement at the APIs directly on entities.
The separation between private and public APIs is a design push in that direction.
Since
update()is private, public APIs are read-only.