Paying attention to the ‘when’ while modelling your domain

when
when

Most textbooks tell us to pay attention to the nouns in a user stories as they will become classes in your domain model and verbs are treated as implementation detail however there are scenarios when the verbs in a user story are first class concepts and need to be modelled as classes, this is often the case where a verb follows when in the user story.

Let me give you an example:

when a member borrows a book, the count of that book in the library should reduce, and that book must get assigned to the user

This leads us to a Member and Book class, with a method called lending/borrowing.

It pays to ask, what else happens when a book is borrowed ?

Should a mail be sent to the member ?

Should we record this transaction in a separate audit table ?

Had we not paid attention to the “when” we may have lost out on some of these subsequent actions which the system needs to do after a book is borrowed.

How do we implement this in code ? a naïve way would be to add method calls after the book lending method call but this approach looks clunky because we are mixing multiple concerns into this method.

This code snippet below implements the naïve approach

issueBook(IssueBooksCommand bookIssueCommand){

Book book = bookRepository.geBookWithUid(bookId);
book.lend(member.getId(), booksAlreadyWithMember);
bookRepository.lend(book);
// Action performed after a book has been issued
emailService.notifyMember(member,book);
// Action performed after a book has been issued
recordIntoBookLending(member,book);
}

Now let’s assume that the client comes up a new requirement.

when a member borrows a high value book, we should deduct a certain amount from the deposit he had
with us, this gets refunded back when the book is returned. This ensures that the member cannot
borrow too many high value books at one go”.

Now what do we do ?, add in one more method call ?, given this new requirement we are modifying our existing method, which means we are violating OCP, Open for extension closed for modification !!!, we are in fact open for modification and closed for extension.

So how do we get OCP, pay attention to the “when”, and then use events to model the when.

So how do we get OCP, pay attention to the “when”, and then use events to model the when.

If we leverage events the original method does not get modified.

issueBook(IssueBooksCommand bookIssueCommand){
Book book = bookRepository.geBookWithUid(bookId);
book.lend(member.getId(), booksAlreadyWithMember);
bookRepository.lend(book);
}

inside book.lend, we raise a event.

{

checkIfMemberAlreadyHasBook(memberId,booksAlreadyWithMember) ;
if (availableCopies <= 0) {
throw new NotEnoughCopiesException(new ErrorDetails.Builder("100").args(Lists.newArrayList(availableCopies.toString(), name)).build());
}
availableCopies--;
domainEventBus.raise(new BookIssuedEvent(bookId, memberId));

}

Once the event has been raised we can have any number of event listeners each doing their own bit, like a BookEventListener class with a emailNotifier method and so on.

Events can be published using a in memory message bus or something more heavy-duty like JMS. A new requirement implies we only add a new Listener we don’t modify the existing method, we are open for extension now.

This is similar to triggers and technically the concept is the same however triggers are data centric rather than domain centric they make you think in terms of tables rows and columns while events make you think about the ubiquitous language of the domain and hence my preference towards using events. An event can be named based on the ubiquitous language.

Will you be listening for the “when” more closely now ?