To compare messaging with other approaches to object orientation, Steve Bate suggested a fun exercise:
I want to take him up on this and asked him, to point me to sequence diagram of his choice we both can tackle. That way we could compare results and learn from each other.
- Here´s what he sent me:
When I looked at this sequence diagram I immediately felt… hm… uneasy. Although the problem domain is not difficult to understand, once you dig into the back and forth of the request-response flow, I find the functionality far from clearly depicted.
Well, that´s how it is with sequence diagrams, I´d say. Activity is flying left and right like a ping pong ball. Very hard to follow.
Let´s see, how messaging (or designing with flows) can clear up the entanglement.
In order to do any design you need a scenario. Just mechanically translating the sequence diagram to some messaging diagram won´t do. My first step in doing Steve´s exercise thus is to “reverseanalyse” the requirements behind the sequence diagram. This is what I came up with:
As a clerk in a video store I want to register video rentals.
A customer will walk up to me with a video request. I then look up if we carry the video. If so, I check if the customer is allowed to rent it (age restrictions might apply!) and if it´s available for rent. If the checks are ok, I register the rental with the customer.
The customer identifies himself with his name, so I can look him/her up in our customer database.
He/She can rent the video, if there´s no reservation pending for the desired rental period.
Outside-in design I - User dialog
To me the sequence diagram looks out of context. I don´t know if classes (?) like Rental or Reservation should exist. Scanning requirements for nouns to manifest as classes to me is a form of premature optimization.
Rather I prefer to start with the inevitable: the outer shell of a software system, its dialogs to make interaction with the environment possible.
- For the above scenario I envision a dialog roughly looking like this:
The user (clerk) can enter a video title to look up. He´s then presented with a list of matching titles in the drop down list of the combobox.
The user then can enter a customer name to look up in a similar way.
Finally he chooses a rental period starting at the same day - that´s just for simplicity´s sake - and hits the “Rent out” button.
- If all´s well, the video is registered with the customer as rented. Otherwise an error message will be displayed.
There are five interactions with the software system through this dialog:
- Look up video; a list of videos is returned
- Chose video
- Look up customer; a list of customers is returned
- Chose customer
- Rent out video
The sequence diagram shows only what happens during the final interaction. That´s what I will do, too.
- Outside-in design II - Interaction flow
- On a first level of abstraction what happens is very easy:
By hitting the “Rent out” button the user fires an event to be processed by the domain logic. Or I could say triggers sending a message from the dialog to the domain logic.
The domain logic processes the messages and outputs either a success or a failure message. The success message is just a signal: all´s well. The failure message, though, contains an error text to be displayed in the dialog.
Even for a casual observer this should be easy to understand. There is a very clear message flow, even a very clear causal connection.
Outside-in design III - Stepwise refinement
Of course this is just an initial design draft. I could use it to implement a walking skeleton to let some Product Owner check if I understood the requirements fundamentally correct. But in the end the domain logic unit “Rent out” shouldn´t be responsible for doing all the hard word.
Rather processing the rental request should be refined. Also because only then the real functionality, I mean, how things are processed, becomes visible.
- This is how it could be done:
Still easy to understand, isn´t it? Data is flowing straightforwardly. Processing advances step-by-step.
If I like, I can now start implementation. But I want to take you still a level deeper. Because that´s where a separation of concerns happens.
- Here´s how “check availability” could look:
The first function unit load the reservations for the video selected. The second checks them against the rental period chosen for the customer.
That´s two different aspects so I think it´s appropriate to put them in different functional units. One is accessing persistent data, the other embodies business domain rules.
- Checking the age restriction could work likewise:
Coloring functional units grey means they are leafs with regard to the decomposition hierarchy. No further refinement is planned.
What about objects?
I hope you find this depiction of the functionality of the interaction easy to understand. Nevertheless you might feel uncomfortable, because… there don´t seem to be proper objects. But there are.
Look at customer and video and reservation. That´s objects, although they might not be as overloaded with functionality as you´re used to. Their primary responsibility (think SRP!) is to carry data. Call them “data entities” if you like.
They may possess functionality to work on the data in addition to field access. But that´s just for consistency´s sake. Consider them ADTs like a stack or queue.
- And then there are other objects doing the data access and the domain logic. I haven´t emphasized them yet, though. But here´s how I see the distribution of functionality:
The functional units of the flow hierarchy are neatly wrapped in highly focused classes. Some represent state (e.g. the repositories). But all are independent of one another. Well, with one exception: the interaction class integrates other functional units, so it needs to know them. But that´s ok, since it does not contain any logic constructs itself. Control statements and expressions are pushed down to the leafs.
That was a fun experience. Taking the sequence diagram and morphing it into a flow design.
To me the hierarchy of flows is much, much easier to read. No back and forth, just messages flowing from left to right.
It´s straightforward to implement. And it´s even easier to test.
Interestingly after implementing I´d throw away these design sketches. I don´t need them anymore. Because the implementation would mirror the design. I could easily re-generate the design on any level of abstraction from the code. That´s much, much harder for a design done with a sequence diagram. You´d need a tool or jump around the code base a lot.
Thank´s Steve for this cool exercise idea. I´ll use it to stay limber in my design skills - and to tease devs attending my design trainings ;-)
P.S. For those you look very closely at the flow drawings and think they spotted inconsistencies regarding the messages: I´m mostly just writing down the data flowing out of a functional unit and what´s needed for the next functional unit. I don´t carry over all data visible in a flow. Neither do I build messages consisting of all data relevant to all functional units.
Trust me, even though this might look a tad inconsistent, it´s easy to understand and does not cause any frictions during implementation.
Plus: Hey, this is design. And design is supposed to be abstract, i.e. lack details of the implementation.
P.P.S. Yes, I left out refining “register rental”. Take it as an exercise in design for messaging for yourself ;-)