Saturday 2 November 2013

DDD and Hexagonal architecture

Since two years ago I had the pleasure the start using DDD at my workplace I can hardly imagine developing software without it (long-haunting experiences from previous projects might have something to do with this). There is a lot to love here: Entities, Value Objects, Repository pattern, Aggregates, Bounded Contexts, Ubiquitous Language, Anti-corruption layers, ... But for me the most valuable part of it is the idea of placing the Domain in the heart of the application and building everything around it as opposed to the traditional layering theory, where the UI is on the top, the Domain in the middle, and everything lies on the DB at the bottom.

----------------------------------
UI Layer
----------------------------------
Business Logic Layer
----------------------------------
Persistence Layer
----------------------------------

This idea, placing the database and the data model at the center, have proved to be very harmful, often resulting in anemic objects and procedural code. And goes against one of the most fundamental concept in OO design, the Dependency Inversion Principle. High level modules should not depend on low level modules. What's so special about databases anyway? What if our application instead of using a DB directly has to cooperate with a legacy system, storing the data through it and communication is based on web-service calls? And before it "stores" the data it communicates with other components through web-services? What I'd like to point out, that if we follow the "traditional" layering model, we have to assign the first WS calls to the Persistence Layer, but the second to the BLL. The distinction is simply arbitrary and contrived.
What DDD does, using an onion-like layering structure instead of the vertical, one-dimensional one, is what Alistair Cockburn proposed as Hexagonal Architecture, or Ports and Adapters Architecture, even before DDD appeared on the scene.



Please follow the link before read further. This could completely change the way how you think about software. Funny enough in spite of being around for almost 10 years by now I have yet to see a nice example on the net. So I'd like to fill this gap now.

Hexagonal (Ports and Adapters) Architecture example

Instead of the usual and boring Pet Clinic or Ordering application I choose to use an unlikely, but at least more interesting theme. Let's build an application that receives captured secret messages from the enemy, asks an other component to break them, then stores the decrypted messages. Here are the classes
//infrastructure layer
class MessageListener {
     void handleMessage(String jsonMessage) {
         EncryptedMessage encryptedMessage = getEncryptedMessageBuilder().build(jsonMessage);
         getCodeBreakerAppService().breakAndStore(encryptedMessage ); 
     }
}
class WSBasedDecrypter implements Decrypter {
   // call a WS to do the work
}
class MongoDecryptedMessageRepository implements DecryptedMessageRepository {
   // store stuff in Mongo
}
//app layer
class CodeBreakerAppService {
   void breakAndStore(EncryptedMessage encryptedMessage) {
        authentiationCheck();
        startTransaction();
        getCodeBreakerAndArchiver().breakAndArchive(encryptedMessage); 
        endTransaction();
   }
}
//domain layer
class CodeBreakerAndArchiver {
   private Decrypter decrypter ;
   private DecryptedMessageRepository decryptedMessageRepository ;
   void breakAndArchive(EncryptedMessage encryptedMessage) {
        DecryptedMessage decryptedMessage = decrypter.break(encryptedMessage);
        decryptedMessageRepository.archive(decryptedMessage); 
   }
}
interface Decrypter {
    DecryptedMessage break(EncryptedMessage encryptedMessage);
}
interface DecryptedMessageRepository {
    void archive(DecryptedMessage decryptedMessage);
}

Imagine the app is listening to a message broker, like ActiveMQ. We have a MessageListener instance, which is configured to listen to a JMS queue. For the sake of simplicity, imagine that the messages coming in are simple JSON strings. So, assuming you've read the article, you can surely indentify the ports and adapters of this app. As a reminder, a port is where our application interacts with the external word, sitting just on the boundaries of the application. We have 3 in our app.

1. The breakAndStore public method on CodeBreakerAppService. This is a driven port, which is called (indirectly) by some external entity. The entry point of our app. The adapter, that transforms the message to something the port can understand is the MessageListener. I said indirectly, because the message have to go through some integration layer (JMS listener mechanism here), then the adapter which eventually passes it to the port.

2. The Decrypter interface. This is a driving port that is called by the domain, triggering some effect on the external world. It is implemented by an adapter, in this case WSBasedDecrypter.

3. The DecryptedMessageRepository interface, similar driving port implemented by the MongoDecryptedMessageRepository as the adapter.

Now let's imagine another team in our company wants to use our app to spy their own enemies, but they abhor NoSQL (shame on them) and want to store the encrypted messages in Oracle. No problem at all. We only have to create a new implementation of the DecryptedMessageRepository interface, let's say JDBCDecryptedMessageRepository, and can plug it in the Domain in runtime if needed. Or if you don't want to store the messages but spit it out on-the-fly to the screen, you can create another implementation sending messages to the UI (although Repository may not be the most appropriate name for it anyomore). The point is, there is no up (UI) and down (DB) here. Just an outer layer of onion wrapping around the core. The adapters are responsible for the details.
Then the Product Owner says we need to open the app not only for JMS, but REST as well. So let's configure MessageListener as a REST endpoint too, or create a new class for that. Our Domain (and application layer) is intact. The Domain independent of all these details, not a single line needs to change.

This architecture style is so simple and elegant, I can't understand why Hexagonal Architecture hasn't become a household name by now. And it hasn't. Most often if I mention it to other developers I meet blank faces. Sometimes it rings a bell, I've failed to meet anyone saying, "yeah it's cool and we use it all the time".

Another interesting thing is that, I think, if you follow DIP, you can't help ending up with this. It just grows out of a very simple principle. That's it for now.

No comments :

Post a Comment