Do want to get a better understanding of Spring web application architecture? If so, get started right now!

Domain-Driven Design Revisited

Business man facing problems

Recently I read a book titled Domain-Driven Design by Eric Evans. This wasn’t the first time I read this book but this time I realized that I had been totally wrong about domain-driven design.

I thought that the domain model consists of entities and value objects. In fact, I was obsessed with moving the domain logic to entities and value objects. I wanted to do it so bad that I ignored a crucial warning.

My code was cleaner than it used to be but I always felt that I was missing something.

When was I reading Domain-Driven Design for the second time, I realized what I had missed.

The Building Blocks of a Domain Model

My mistake was that I was so obsessed with entities and value objects that I forgot the other building blocks of a domain model.

Domain-Driven Design describes the domain model as follows:

A domain model is not a particular diagram; it is the idea that the diagram is intended to convey. It is not just the knowledge in a domain expert’s head; it is a rigorously organized and selective abstraction of that knowledge. A diagram can represent and communicate a model, as can carefully written code, as can an English sentence.

However, reading this definition did not change my mind. In fact, when I read it I was feeling proud of myself because it seemed to verify that I was doing the right thing.

Then I started reading the second part of the book which talked about the building blocks of a model-driven design and the house of cards, which I had so carefully crafted, fell apart.

This part argues that the model-driven design has the following building blocks:

  • Entities are objects which are defined by their identity. In other words, if an object has an identity which stays unchanged through its entire lifecycle, this object should be modelled as an entity.
  • Value objects describe a property of a thing, and they don’t have their own identity or lifecycle. Often their lifecycle is bound to the lifecycle of an entity.
  • Services contains operations that don’t belong to entities or value objects. If adding an operation to an entity or value object doesn’t feel natural to you, the odds are that you should add this operation to a service.
  • Modules (Packages) are used to reduce cognitive overload. They give a developer the possibility to either investigate the internals of a single module without paying attention to other modules or investigate the relationships between modules without paying any attention to the implementation details.
  • Aggregates are groups of objects which are treated as a single unit. Each aggregate has a root object which is used to access the other objects of the aggregate. Each aggregate has also a boundary which defines what objects belong to the aggregate in question.
  • Factories are used to encapsulate the creation logic of an object or an aggregate. Factories are useful if the creation logic is complicated or reveals too much about the internal structure of the created object.
  • Repositories are used to fetch entities from the used data storage and save the information of entities to it.

After I had finished the book, I had no other choice than to admit that I had no idea what domain-driven design really is.

The good news is that I still have got plenty of time to learn.

What Did I Learn?

The biggest thing which I learned by reading the Domain-Driven Design for the second time should be pretty obvious now but I did pick up a few other lessons as well:

  • I understood the difference between the application services and the domain services. Application services coordinates the tasks and delegates work to domain objects. Domain services implement operations which don’t belong to entities or value objects. In other words, application services don’t contain business logic and domain services contain it.
  • I understood that the domain model doesn’t have to be an exact copy of the reality. I can simply pick the parts of the reality which are useful to me and forget the rest. This seems obvious at first but it is also very easy to forget this.

My next step is to learn more about domain-driven design. To be more specific, I want to figure out how I can use these building blocks in my daily work. That is why I already ordered Implementing Domain-Driven Design by Vaughn Vernon.

What is your next step?

If you enjoyed reading this blog post, you should follow me on Twitter:

About the Author

Petri Kainulainen is passionate about software development and continuous improvement. He is specialized in software development with the Spring Framework and is the author of Spring Data book.

About Petri Kainulainen →

8 comments… add one

  • Hi Petri, looks like we are moving along the same path – first the DDD Bible, now the book by Vernon. Thanks for this post – nice recap of the DDD foundations. Cheers!

    Reply
    • Hi Tomek,

      Thank you for your kind words! Yes, it is actually quite interesting to see that we are interested in the same things (unit testing and DDD). I will probably write more about DDD after I have read Vernon’s book and it would be interesting to exchange our notes about this.

      Reply
  • Every time I read “blue book” I learn something new. Every single word in the book is important and sometimes it is really easy to misunderstand the concept. When I started with DDD I also focues on building blocks, the easiest part. Right now I appreciate more and more “strategic design” part, the most valuable part of the book.

    Reply
    • Hi Marcin,

      that is a good point. The strategic design is not crystal clear to me (yet). I guess I will have to pay more attention to that when I read that book for the third time.

      Reply
  • Hi Petri,

    first, I must say that I’ve visited your blog very often in the last few months. That’s because I was searching for specific things you described best and the way you see “coding” is very similar to the way I see it.

    I started a private project and used DDD (actually I tried). I find DDD very convenient but currently I came across a problem and do not know how to solve it – maybe you can help me with your experience. I have a domain entity with a property storing encrypted data. This entity also have a method to create an other object. But to do so, the encrypted data has to be decrypted first and I need a service for that. The problem is that I can not access that service from the entity (without passing it as a method parameter).

    
    class MyEntity {
       private byte[] encryptedString;
       public MyOtherEntity createOtherEntity() {
          return new MyOtherEntity().setValue(decryptionService(encryptedString));
       }
    }
    
    

    This example is very simplified – the point is, that passing the decryptionService as an argument is not a good solution, because I need the decryption only in a special case (not every time).

    I played around a lot but did not find a proper solution. The best solution I found is to have a static method in the decryption service. Maybe you have a better solution – I would appreciate it!

    Thank you and keep on blogging,
    best regards
    Daniel

    Reply
    • Hi Daniel,

      That is a very good question.

      Does the component that calls the createOtherEntity() method of the MyEntity class know if the decryption is required? If this component knows this, you could create a DecryptionService interface and provide two implementations for it:

      • The DecryptionServiceImpl class would decrypt the encrypted string.
      • The NoOpDecryptionService class would simply return the encrypted string.

      The source code of your entity would looks as follows:

      
      class MyEntity {
         private byte[] encryptedString;
         public MyOtherEntity createOtherEntity(DecryptionService decryptionService) {
            return new MyOtherEntity().setValue(decryptionService.decrypt(encryptedString));
         }
      }
      
      

      If decryption is not required, you can create a new MyOtherEntity object by using this code:

      
      MyEntity entity = ...
      MyOtherEntity otherEntity = entity.createOtherEntity(new NoOpDecryptionService());
      
      

      If decryption is required, you can create a new MyOtherEntity object by using this code:

      
      MyEntity entity = ...
      MyOtherEntity otherEntity = entity.createOtherEntity(new DecryptionServiceImpl());
      
      

      On the other hand, if the caller of the createOtherEntity() method doesn’t know if the decryption is required, you could still pass the decryption service as a method parameter and use it only if decryption is required.

      I think that your solution isn’t bad either. The reason why I want to pass the decryption service as a method parameter is that it makes testing a lot easier.

      What do you think about this solution?

      Reply
      • Hi Petri,

        thanks for your answer. The problem is, that my example was very simplified. I will try to be more specific.
        The caller has no knowledge about decryption and the method did not imply that. Actually, the caller is calling a different method than the method, who need the decryption. I will explain that: I have an abstract class A and two subclasses B and C. The caller is calling a method from A and that method is executing a method in either B or C. B needs decryption, C doesn’t.

        
        abstract class A {
             public void doSomething() {
                  // do some stuff
                  doMore();
             }
             protected abstract void doMore();
        }
        
        class B extends A {
            private String encryptedString;
            protected void doMore() {
                 String test = decryptionService.decrypt(encryptedString);
            }
        }
        
        class C extends A {
              protected void doMore() {
                    String test = "Hello World";
              }
        }
        
        

        That’s why I did not pass the decryption service as an argument. It is only need in a special case and it would be confusing to pass that service, because doSomething has absolutely nothing to do with decryption.

        If A, B and C wouldn’t be entities but services, everything would be easy (using DI). But because both methods are part of an entity (and I want to leave it that way) I have a problem now.

        Thanks for your help,
        best regards,
        Daniel

        BTW: I bought the book by Vaughn Vernon you recommend and started reading it. I am very satisfied with it.

        Reply

Leave a Comment