- Why are getters and setters unsafe? Anemic Domain Model vs Rich Domain Model
- Special offer from CodeGym
When we write code, we try to hide as many details as possible - using appropriate access modifiers. Most often we use getters and setters methods to set the appropriate state of an object. Is this a good practice? In this article you will learn what is Anemic Domain Model and Rich Domain Model and which way of creating models is the best. What’s more, you’ll find a special opportunity from CodeGym at the end of this post. I invite you to read :)
Why are getters and setters unsafe? Anemic Domain Model vs Rich Domain Model
Take a look at the code below:
Do you see any potential risks? That’s right! The class contains public fields that can be changed anywhere else in the application code. It will be hard for us to control such an application, especially if it becomes a big project.
All right. Let’s make changes so that the fields are not public:
Does it look familiar? You have certainly come across such a code before. In accordance with best practices - fields are private. But adding getters and setters does not improve the situation (because we have the same access to variables - as if they were public). So how to deal with this problem? The answer is the Rich Domain Model you are about to read about.
Why is public unsafe?
Let’s go back to the first example.
User class above exposes its implementation, which in this case is very dangerous. Why? We can freely manipulate all the fields in this class. You can set any field without any consequences. Another problem can be changing the user id at any time in the code.
Anemic Domain Model in Java
A class with state exposed by getters and setters is an Anemic Model. An anemic domain model is often used as DTO (Data Transfer Object) object - a class with private fields and setters and getters. With an anemic approach, our models become just “containers” for the data, and all the logic for operating on that data is extracted to classes such as:
What do you think the
UserService class might look like? Let’s put together some requirements:
- The user at creation is non-active. Only after email confirmation his account is activated,
- By default, the user is not blocked. In case of breaking the rules, the user can be blocked by the administrator,
- User email address must be from gmail.com domain,
- User name must be shorter than 100 characters.
Here we go! Following the most popular pattern, we create individual services:
UserActivationService - service responsible for user activation. A user can only be activated once!
UserBlockerService - service responsible for blocking users. As with activation - a user can only be blocked once. It is not possible to block a previously blocked user.
UserRegistrationService - service responsible for user registration. According to the above requirements, we need to validate the username length and email address.
So we have a simple, anemic User domain model that contains no logic. We also have 3 services (
UserRegistrationService) with all the logic of the user activation, blocking and registration. This solution is very popular but has a few cons:
- It simply violates the “Tell, Don’t Ask” principle which states that objects should tell the client what they can or cannot do rather than exposing properties and leaving it up to the client to determine if an object is in a particular state for a given action to take place.
- We often have to repeat code across multiple places. In the case of our project - in UserBlockerService and UserActivationService we had to repeat checking if the user already exists.
- The model is completely untestable because we cannot ensure that the model doesn’t get invalid at some point. Theoretically, some other **Service could create a user with a disallowed domain in email and add it to the database - which would break one of our requirements.
Rich Domain Model in Java
Now let’s look at the same example in the rich domain approach. Let’s start with the email field. We can move it to a separate class -
Now do the same for the Username field:
As you can see the classes are similar. They have validation and a static method to create an object (Static Factory Method). It is not possible to create an Email object with an invalid domain and a Username object with a too long name (as required). Below I will show you the code of a rich
activate() methods I used
checkState() from Guava library. If the first parameter is false - we throw
IllegalStateException with the content from the second parameter. Thanks to this we do not have to create additional “if” conditions.
As you can see, compared to the anemic model our rich domain model does not provide the ability to modify fields externally. There are no setter methods. Variables have private access, they are set in a private constructor. There is a public static factory method that creates a new User object. Additionally, we have some getter methods to be able to extract some information (such as username or userID).
The most important thing about the above code is that the rich domain object provides business methods, exposing the behavior of the model and not just its state (as for standard models with getters/setters).
The main advantage of the Rich Domain Model
A rich domain does not allow us to create invalid objects. You are not able to create a user with a wrong email address or too long username - as in the anemic domain model. The state of an object is always consistent, and consistency is guaranteed by the object itself, not by conditions checked in code that operates on objects (such as
If we use an anemic model - we make our object public, allow for any modification of the model variables and all the responsibility (logic) is transferred to external classes of the Service type. This creates a lot of IF conditions and a big risk that before performing some operation we forget to check some condition that would exclude the possibility of performing that operation.
If the logic is distributed among many services, and the model itself allows any modification of its internal data - we are very exposed not only to making a mistake, but also to overwriting the field value, unauthorized/uncontrolled access to the field. By moving the same logic into the domain model itself - there is less chance of error.
Example tests of a rich domain model
Testing the rich model is straightforward. Below you can see what a sample test looks like:
When to use getters and setters in Java objects?
You can safely use getters/setters in DTO (Data Transfer Object) objects. DTO models are used as entities when communicating with databases or as data transfer objects between REST services. Often setters are even necessary because libraries of various frameworks use them. But remember that it is best to map such objects to domain objects (which will use a rich model).
Special offer from CodeGym
CodeGym is an interactive educational platform where people can learn Java programming from scratch. I used this platform early in my career and it was great to solidify my knowledge of Java. It takes a very practical approach to learning programming. Also, it does not neglect the theory part which is also very important. I would recommend it to anyone who wants to learn java or prepare for a job interview or new job. Additionally, CodeGym creates a community where you can get help or meet interesting people.
30% Off With CodeGym subscription by sending the coupon: devdiaries30 to email@example.com
If you are interested in full access - CodeGym has provided my blog readers with a 30% off discount coupon. All you need to do is to send the coupon: devdiaries30 to firstname.lastname@example.org. If you have any questions, please do not hesitate to contact me!