We are building some business modules that must be intuitive for developers to use, so the code itself explains and forces developers to use it in certain way. This applies both for enhancing the existing features (by our developers) or reusing it (by 3rd party developers). All modules are part of big infrastructure, so they share same entities (like User), but operate on their own entities. It’s Java 7 (meh).
Here are some principles that popup.
- don’t encapsulate business in one method, so we can have more flexibility on reusing it in different way (think functional here)
- hide models completely from developers, because no matter how we tried, models always represents different representations depending on use case; plus there is some tech info in models we do not want to share with the devs. This is a step forward from making them immutable.
- don’t generalize; try to force the usage for the users.
It’s hard to explain this, so let me try to give you an overview how this business layer should be used:
- first specify the context you are working with. The context is a composite of models, that simulates the aggregate.
- on the context you can specify the model action (don’t have a better name at the moment): this is a DSL to change the model. user can change only what this dsl allows.
- on the context then you can perform one or more commands (chainable). This time, this is DSL over the business.
- finally you may invoke the renderer which purpose is to render the context into the any view class needed for the UI.
Here is how this looks like (don’t be hard on class names, they are not best yet):
PollsService polls = new PollsService();
PollsView pollsView = polls.newPoll(new ModelAction<PollsQuestionModelBuilder>() {
@Override
public void execute(PollsQuestionModelBuilder question) {
question.title(title);
question.choices(choices);
if (!expiration) {
question.doesNotExpire();
}
}
}).update(new Command<UpdateService>() {
@Override
public void execute(UpdateService updater) {
updater.savePollQuestion();
updater.savePollChoices();
}
}).render(new ModelView<PollsView, PollsQuerier, PollsQuestionAndChoiceView>() {
@Override
public PollsView render(PollsQuerier pollsQuerier, PollsQuestionAndChoiceView data) {
return new PollsView(data.getPollsQuestion(), data.getPollsChoices());
}
});
At one point this can be written as:
PollsView pollsView = polls
.newPoll(fromRequest(request))
.update(pollsUpdate())
.render(defaultView());
Here is another example on update:
PollsService polls = new PollsService();
polls.on(questionTicket, new ModelAction<PollsQuestionModelUpdater>() {
@Override
public void execute(PollsQuestionModelUpdater pollsQuestionModelUpdater) {
pollsQuestionModelUpdater.changeTitle("Why do you do this?");
}
}).update(new Command<UpdateService>() {
@Override
public void execute(UpdateService pollsQuestionUpdater) {
pollsQuestionUpdater.savePollQuestion();
}
});
So it all about above steps, in functional way (as much java7 allows it). As developer, you need to be aware of your context and all the changes you do, they must reflect the context. If you need more – create new context i.e. new aggregate. So as developer you have to think about:
- what you are working with
- which operation to perform on top of it
- and how data should look like.
Of course, step 2 and 3 are not mandatory, i.e. you can have view without operation and vice-versa.
Did anyone used similar approach? and why?
9
… intuitive for developers to use, so the code itself explains and forces developers to use it in certain way…
-
It’s a myth that OO bits can be intuitive unless they are trivial.
like the most fundamental framework elements. LOB objects are the
opposite of that. They need good documentation and examples
demonstrating all the functionality you’d hope to use. If you have
unit tests, you can use some of those for examples. Think of how
much googling it takes just to consume some frameworks and these are
generally less complex than business rules. -
It might not be the best idea to force devs to use it in a certain
way. Especially if you’re hoping it’s intuitive. Even top devs will
‘intuit’ all kinds of random ways to use objects if all they have to
go on is the list of types and members.
So, If you’re not giving the devs your cell#, plan on spending a lot of time generating documentation!