DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Understanding Aggregates in Domain-Driven Design
  • Introduction to Spring Data JPA, Part 3: Unidirectional One to Many Relations
  • Monolithic First
  • Providing Enum Consistency Between Application and Data

Trending

  • The Perfection Trap: Rethinking Parkinson's Law for Modern Engineering Teams
  • The Role of AI in Identity and Access Management for Organizations
  • Navigating Change Management: A Guide for Engineers
  • Building a Real-Time Audio Transcription System With OpenAI’s Realtime API
  1. DZone
  2. Data Engineering
  3. Databases
  4. Protecting Your Domain-Driven Design from Anemia

Protecting Your Domain-Driven Design from Anemia

What do Monopoly and domain-driven software development have in common? Check out the answer and 4 ways to keep your domain encapsulated and avoiding anemia.

By 
Stuart Meikle user avatar
Stuart Meikle
·
Oct. 02, 20 · Tutorial
Likes (11)
Comment
Save
Tweet
Share
10.3K Views

Join the DZone community and get the full member experience.

Join For Free

Proviso: I largely refer to Java implementation aspects in this article but the principles are transferable to other languages


Sometimes with my children, when the nights draw in, we play board games to pass the time. Monopoly is a favorite. Of course, it comes with the board and counters, the cards and the money. There's a rule book too in the box but that is largely left to the side; we know the rules already. We put out the board, place the counters and cards, and begin to play the game. It's usually a great deal of fun.

There are two different aspects of what's happening here which I'd like to draw your attention to; there are the physical elements of the game - the board, the counters, the money, and so on - which live in the box most of the time, and there are the non-physical elements such as the rules, which as mentioned, generally live in our minds.

These two aspects - the physical and the non-physical - have different properties, different life cycles, and are used in different ways. They are both needed together to play the game.

In domain-driven software development, there is a similar situation: there are data fields and structures, (which we might liken to the physical pieces of the game), and there is the programming logic, or business logic, which governs how the data fields can be changed and manipulated. Software needs to use both aspects together to produce an application or microservice, but in many senses, these two things are quite different and have different requirements and life cycles. This is summarized in the table below.


Physical

Non-physical

When both exist together as a whole 

Application data fields business logic rich domain model
Game game pieces rules full game set

Consider how the data fields in a micro-service class might be used. They will generally exist as part of a domain class. Methods within the class operate on them and change their values, they validate them and ensure the class as a whole is always consistent and makes sense. They form part of the rich domain model. As they represent data their life goes beyond this also - the class might be persisted to disk or database or be placed into an in-memory cache. They might be converted into JSON and used in an external web service call. Often they don't only exist as part of the domain model but they have these additional lives elsewhere.

The business logic, on the other hand always lives as part of the domain model, is often attached to a specific class and operates on a specific set of data. It is never persisted as an entity in a database or in a file because it never changes. (Or never changes without an upgrade of the application, at least). It does not need to be transformed in different ways to suit its different usages. It is static.

The figure below shows a simple example. "Product" is a class existing within a domain model. The business logic pertaining to the Product class can exist within the class itself and the business logic for the domain can be tested in isolation. The Product class contains data fields and these can be persisted to a database. As part of that process the data fields are first wrapped in another class, and then transformed into a JPA entity before being persisted. In this example a version is added to the data to assist with optimistic locking in the database. 

product related business logic , domain model and persistence graph

As a second example, if I wish to store the data in a class into an in-memory data grid, such as Hazelcast, I need to transfer the data into an object which can be serialized and which has a unique key. (Keys in Hazelcast need to provide identity without considering the content of the data). Again we need to add information to the domain model in order to use the data in other places or with other tools.

So our code has these two aspects -- data and business logic -- and they are quite different. They serve different needs and are handled in different ways. We are taught to nevertheless bind the two together and build rich domain models. Martin Fowler mentions "A domain model mingles data and processes, has multi-valued attributes and a complex web of associations, and uses inheritance." (PoEAA)

It's great if we can do this and have all the logic and validations associated with a given domain class living within the class, but as we have seen above the reality is often that the data needs to be stored, retrieved, cached and so on, and the business logic does not.

We are caught between a rock and a hard place. If we extract the data fields from the business logic in order to support the different operations we need to do to the data, then we may be left with POJOs or DTOs; objects that Martin Fowler describes as 'hateful' and 'things our mothers told us never to write' (PoEAA again). In the worse case our domain model will become "anemic". You can read about "anemic domain models" here ( AnedmicDomainModel ). Others describe anemic domain models as the anti-pattern of the devil! (ADM2). Clearly there are demons here. 

On the other hand if we don't separate the data and logic we may need to add additional info to our domain model which is not directly relevant, which adds clutter and binds the domain model to other layers of our architecture. It may even prevent us using some open source technologies or force us to add additional untidy code to work around this abstraction.

A compromise is needed! We need to find a way to avoid the pitfalls of an anemic domain model whilst still allowing us to separate the data fields from the business logic if we need to. How can we do that? Well the first consideration is 'Why are anemic domain models bad in the first place?' Anemic domain models lead to business logic becoming spread throughout your application. The services layer, for example, may do more than just co-ordinate changes, it may include rules for how the domain objects are created and changed. We might find multiple parts of the application attempting to do the same domain changes in different ways and perhaps ending up with different results. We can't in general manage access to the domain model and its business logic with an anemic domain model. 

Therefore if we are forced to separate data and business logic these still both need to reside together, tightly coupled, in the domain layer. There needs to be clear rules about how to create and change the model and ideally an interface to initiate those changes which can be used by other layers of the application.

What techniques can we use, then, to ensure access to the domain model is controlled and managed? Here are four:

1. Use Package-Level Setters

put the domain business logic in the same package as the POJOs and make the POJO fields private. Provide public getters and make the setters accessible only from the package. The code snippet below shows this:

Java
 




x
24


 
1
package domain.encapsulation.example;
2
 
          
3
public class Product {
4
 
          
5
    private int id;
6
    private String name;
7
    private String description;
8
    private double width,height,depth;
9
 
          
10
    //public access
11
    public int getId() { return id; }
12
 
          
13
    //package level access
14
    void setId(int id) { this.id = id; }
15
 
          
16
    //public access
17
    public String getName() { return name; }
18
 
          
19
    //package level access
20
    void setName(String name) { this.name = name; }
21
 
          
22
    ...
23
}
24
 
          


 This has the advantage that only classes within the same package can change the data fields in the domain model, so business logic can't get strewn throughout the application, and it allows you to provide interfaces in to your model to ensure it is modified in a controlled manner. It means that the whole of your domain needs to exist within a single java package which isn't ideal. Package-level access in Java does not allow sub-package classes to access the fields of classes in packages higher up the hierarchy. 

2. Use a Fully Parameterised Constructor

Use a constructor taking all the parameters and then make the setters private. 

Java
 




xxxxxxxxxx
1
33


 
1
package domain.encapsulation.example;
2
 
          
3
public class Product {
4
 
          
5
    private int id;
6
    private String name;
7
    private String description;
8
    private double width,height,depth;
9
 
          
10
 
          
11
    public Product( int id, String name, String description, double width, double height, double depth ){
12
        this.id=id;
13
        this.name=name;
14
        this.description=description;
15
        this.width=width;
16
        this.height=height;
17
        this.depth=depth;
18
    }
19
 
          
20
    //public access
21
    public int getId() { return id; }
22
 
          
23
    //private access
24
    private void setId(int id) { this.id = id; }
25
 
          
26
    //public access
27
    public String getName() { return name; }
28
 
          
29
    //private access
30
    private void setName(String name) { this.name = name; }
31
 
          
32
    ...
33
}


This is fine if you never want anything to change your class once it has been created. In that case you could also make all of the fields 'final' and you'd end up with an immutable object. That is something more suited for Value Objects rather than Domain Objects. Nevertheless you could use a public constructor which takes all initial field values as parameters together with the restricted access to the setters from (1) to allow instances of your domain objects to be created in a controlled manner. 

3. Use the Factory Pattern

If you make your object constructor private and then use a Factory class to create different sub-classes this would also give you control over how your domain classes are created and validated. Some of your business logic can then reside in the factory class. However it adds complexity - your POJO might become a set of different derived classes, each with different data fields, which in some senses takes you away from what you need; a simple object with externalized business logic. It doesn't really prevent developers directly accessing the sub-classes either, so only offers limited restrictions. 

4. Control Cross-Package Access

Finally there are slightly more exotic ways to govern access to your domain; let's go through these in more detail. In the (pseudo) code examples below we have a POJO domain object -- Product -- and we use a static method call - AccessControl.checkAuthorized() - to protect access to one of the setters. AccessControl makes use of the Java stack to examine which part of the application is calling the setter. If the caller of the setter is a class which has a package name which does not match our domain package prefix then an exception is thrown and the setter cannot be called. 

Java
 




xxxxxxxxxx
1
15


 
1
package domain.encapsulation.example.model;
2
import domain.encapsulation.example.security.AccessControl;
3
 
          
4
public class Product {
5
 
          
6
    ...
7
 
          
8
    public void setName(String name) {
9
        AccessControl.checkAuthorized();
10
 
          
11
        this.name=name;
12
    }
13
 
          
14
    ...
15
}


Java
 




xxxxxxxxxx
1
25


 
1
package domain.encapsulation.example.security;
2
 
          
3
public class AccessControl {
4
 
          
5
    private static final String DOMAIN_PACKAGE = "domain.encapsulation.example.model.";
6
 
          
7
    public static void checkAuthorized() {
8
    
9
        //Examine the stacktrace to ensure calling method has same package prefix as the domain
10
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace();
11
 
          
12
        if (stackTraceElements.length>=4) {
13
            String resourceBeingAccessed = stackTraceElements[2].getClassName();
14
            String accessAttemptedFrom   = stackTraceElements[3].getClassName();
15
 
          
16
            if (resourceBeingAccessed.startsWith(DOMAIN_PACKAGE) && accessAttemptedFrom.startsWith(DOMAIN_PACKAGE)) {
17
                //In this case access to the setter is authorized
18
            } else {
19
                //A method in a non-domain package has tried to access the setter, so throw an exception
20
                throw new RuntimeException("Access violoation. " + accessAttemptedFrom + " attempting to access " + resourceBeingAccessed);
21
            }
22
        }
23
    }
24
}
25
 
          


For example, we might have a class in our application's service layer called domain.encapsulation.example.service.ProductScanner . The package of the class here is "domain.encapsulation.example.service" which differs from that of our domain model, which is "domain.encapsulation.example.model" . Thus if the service class attempts to call the (public) setter in our Product class, the AccessControl will prevent it and throw an exception.

There are a couple of points here to be careful of:

  • We're using the stack to ensure that our POJO domain objects are only accessed from packages which we define as being 'inside' our domain layer. That allows our business logic, which exists within our domain layer to create and change the domain objects as it needs to but prevents other layers of our application having direct access to our POJO setters. However, the penalty we pay here is that Thread.currentThread().getStackTrace() is very slow to execute and so isn't something we would want to use continually as we create and modify our domain objects. 
  • Java does not support nested packages. Although it might look as though domain.encapsulation.example.model and domain.encapsulation.example.service are both subpackages of domain.encapsulation.example, this is not how Java interprets things. From the Java perspective these two packages are entirely separate.  Nevertheless as human beings we often organise the code in our applications in a hierarchical fashion. We might have a package structure such as that below and we can then use that structure to define which parts live in the domain layer and which are other parts of our software. And, of course, we can then use that to allow access as needed.
application.domain
application.domain.product
application.domain.shipping
application.service.search
application.service.ordermanagement
application.util

As it stands this is a little clunky: we're making calls to static class methods throughout our domain model and these perform poorly. We can tidy things up using Aspect Orientated programming (ref . https://en.wikipedia.org/wiki/Aspect-oriented_programming ). I've show this in the code snippets below.

Java
 




xxxxxxxxxx
1
13


 
1
package domain.encapsulation.example.model;
2
 
3
public class Product {
4
 
5
    ...
6
 
7
    @PackageAccessOnly(prefix="domain.encapsulation.example.model.")
8
    public void setName(String name) {
9
        this.name=name;
10
    }
11
 
12
    ...
13
}


Java
 




xxxxxxxxxx
1
19


 
1
@Aspect
2
public class AccessControlAspect {
3
     
4
    ...
5
    @Around("callAt(packageAccessOnly)")
6
    public Object around(ProceedingJoinPoint pjp, PackageAccessOnly packageAccessOnly) throws Throwable {
7
 
8
        //much as in AccessControl.checkAuthorized
9
        ...
10
        if (accessAttemptedFrom.startsWith(prefix)) {
11
            //In this case access to the setter is authorized
12
        } else {
13
            throw new AccessControlException("Access violation. " + accessAttemptedFrom + " attempting to access " + pjp.getSignature());
14
 
15
        }
16
 
17
        return pjp.proceed();
18
    }
19
}


Here we use an annotation "PackageAccessOnly" to define which setter methods can be called from which package prefix. Then we use an Aspect to provide the implementation and control the authorization. We've explicitly defined 'prefix' in the annotation here but you could also use the current package as a sensible default. Using Aspects here with annotations allows us to remove the authorization checks using the aspect configuration, so that our production code does not need any performance penalties from our stack access, but our test code can leave the authorization in place to ensure we protect our domain.

This provides a convenient way to manage access to the domain model POJOs, allowing the business logic to be slightly removed from the domain POJOs themselves and thus also allowing the POJOs to be wrapped and transformed as need be for persistence and caching and so on.

In the end the relevant part of our class hierarchy might look like:

application.domain
application.domain.product
application.domain.product.Product
application.domain.product.ProductManager
application.domain.shipping
application.service.search
application.service.ordermanagement
application.util

Product is our product POJO which we can transform elsewhere as needed. ProductManager contains the product related domain logic and has access to the Product setters. It provides a public set of methods which other layers of the application can use to transform the domain in a controlled manner. Classes in the application.service.search domain, for example, would not have access to the setters of Product and would need to use the ProductManager to access the product part of the domain. We still have a rich domain model here as all domain logic and fields are encapsulated but this richness is not occurring at the class level but at the slightly higher package level.

We can summarize our findings by extending the game analogy and the table we had above:


Physical

Non-physical

Both together as a whole 

Both disconnected

Exist alongside each other with managed access

Application data fields business logic rich domain model anemic domain model, business logic strewn throughout application and inconsistently applied data and business logic live side by side in one specific place with managed access from the rest of the application
Game game pieces rules full game set everyone making up their own rules, no consistency, chaos the rule book might be lost but everyone agrees on a new set and applies them consistently

Summary

We all want to use domain-driven design and use rich domain models to create applications that mirror our business objects and the business logic associated with them. Data and logic, however, have different lives within our code base and are handled and transformed in different ways. This means that even with the best will in the world, sometimes our domain models will become anemic. Provided we are careful to ensure that the business logic remains with the domain model and isn't spread throughout our application or UI, we can mitigate the negative effects of that anemia and Martin Fowler's mother can stay happy.

Business logic Domain model Domain-driven design Data (computing) application Database Object (computer science) Java (programming language) Design

Opinions expressed by DZone contributors are their own.

Related

  • Understanding Aggregates in Domain-Driven Design
  • Introduction to Spring Data JPA, Part 3: Unidirectional One to Many Relations
  • Monolithic First
  • Providing Enum Consistency Between Application and Data

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!