Using the State pattern in a Domain Driven Design
- for most software projects, the primary focus should be on the domain and domain logic
- complex domain designs should be based on a model.
DDD promotes a creative collaboration between technical and domain experts to iteratively cut close to the conceptual heart of the problem. Note that a technical expert may not fully understand the domain intricacies without help from an expert in that field, while a domain expert cannot practically apply his knowledge without the help of a technical expert.
In many cases, a domain model object encapsulates an internal state, which in essence is the element’s history, i.e. the objects operates in a stateful manner. In that case, the object holds its private state which eventually affects its behavior. To represent an object’s state as well as and handle its state transitions in a clean way, the State Design Pattern can be used. In short, the State pattern is a solution to the problem of how to make behavior depend on state.
It is apparent that DDD and the state design pattern are closely linked. I am new to DDD so I will let one of our finest JCG partners, Tomasz Nurkiewicz, to introduce you to DDD with an example that uses the State Design Pattern.
(NOTE: The original post has been slightly edited to improve readability)
Some domain objects in many enterprises applications include the concept of state. State has two main characteristics:
- the behavior of a domain object (how it responds to business methods) depends on its state
- business methods may change the object’s state forcing the object to behave differently after a particular method has been invoked.
If you can’t imagine any real-life example of domain objects’ state, think of a Car entity in a rental company. The Car, while remaining the same object, has an additional flag called status, which is crucial for the company. The status flag may have three values:
- AVAILABLE
- RENTED
- MISSING
It is obvious that a Car in RENTED or MISSING state cannot be rented at the moment and the rent() method should fail. But when the car is back and its status is AVAILABLE, calling rent() on Car instance should clearly, apart from remembering the customer who rented the car, change the car status to RENTED. The status flag (probably a single character or an int in your database) is an example of objects’ state, as it influences the business methods and vice versa, business methods can alter it.
Now think for a while, how would you implement this scenario which, I am sure, you have seen many times at work. You have many business methods depending on current state and probably a number of states. If you love object oriented programming, you might immediately thought about inheritance and creating AvailableCar, RentedCar and MissingCar classes extending Car. It looks good, but is very impractical, especially when Car is a persistent object. And actually this approach is not well designed: it is not the whole object that changes, but only a piece of its internal state – we are not replacing object, only changing it. Maybe you thought about a cascade of if-else-if-else… in each method performing different task depending on state. Don’t go there, believe me, it is the path to the Code Maintenance Hell.
Instead, we are going to use inheritance and polymorphism, but in a more clever way: using the State GoF pattern. As an example I have chosen an entity named Reservation which can have one of the following statuses:
The life-cycle flow is simple: when the reservation is created, it has NEW status (state). Then some authorized person can accept the reservation, causing for example the seat to be temporarily reserved and sending the user an e-mail asking him to pay for the reservation. Then, when the user performs money transfer, money is accounted, ticket printed and a second e-mail is sent to the client.
Of course you are aware that some actions have dramatically different side-effects depending on Reservation current status. For example you can cancel the reservation at any time, but depending on the Reservation status, this may result in transferring the money back and cancelling reservation, or only in sending the user an e-mail. Also some actions have no sense in particular statuses (what if the user transferred money to an already-cancelled reservation) or should be ignored. Now imagine how hard it would be to write each business method exposed on the state machine diagram above, if you had to use if-else construct for every status and every method.
To address this problem, I will not explain the original GoF State design pattern. Instead I will introduce my little variation over this pattern using Java enum capabilities. In lieu of creating an abstract class/interface for state abstraction and writing an implementation for each state, I have simply created an enum containing all available states/statuses:
public enum ReservationStatus { NEW, ACCEPTED, PAID, CANCELLED; }
Also I created an interface for all the business methods depending on that status. Treat this interface as an abstract base for all states, but we are going to use it in a slightly different way:
public interface ReservationStatusOperations { ReservationStatus accept(Reservation reservation); ReservationStatus charge(Reservation reservation); ReservationStatus cancel(Reservation reservation); }
And finally the Reservation domain object, which happens to simultaneously be a JPA entity (getters/setters omitted, or maybe we can just use Groovy and forget about them?):
public class Reservation { private int id; private String name; private Calendar date; private BigDecimal price; private ReservationStatus status = ReservationStatus.NEW; //getters/setters }
If Reservation is a persistent domain object, its status (ReservationStatus) should obviously be persistent as well. This observation brings us to the first big advantage of using enum instead of abstract class: JPA/Hibernate can easily serialize and persist Java enums in the database using enum’s name or ordinal value (by default). In original GoF pattern we would rather put ReservationStatusOperations directly in the domain object and switch implementations when status changes. I suggest to use enum and only change the enum value. Another (less framework-centric and more important) advantage of using enum is that all possible states are listed in one place. You don’t have to crawl your source code in search for all implementations of base State class – everything can be seen in one, comma-separated list.
OK, take a deep breath, now I will explain how all the pieces work together and why, on earth, business operations in ReservationStatusOperations return ReservationStatus. First, you must recall what actually enums are. They are not just a collection of constants within a single namespace like in C/C++. In Java, an enum is rather a closed set of classes that inherit from a common base class (e.g. ReservationStatus), which in turns inherits from Enum. So while using enums, we might take advantage of polymorphism and inheritance:
public enum ReservationStatus implements ReservationStatusOperations { NEW { public ReservationStatus accept(Reservation reservation) { //.. } public ReservationStatus charge(Reservation reservation) { //.. } public ReservationStatus cancel(Reservation reservation) { //.. } }, ACCEPTED { public ReservationStatus accept(Reservation reservation) { //.. } public ReservationStatus charge(Reservation reservation) { //.. } public ReservationStatus cancel(Reservation reservation) { //.. } }, PAID {/*...*/}, CANCELLED {/*...*/}; }
Although it is tempting to write ReservationStatusOperations in such a manner, it is a bad idea for long term development. Not only the enum source code would be extensively long (total number of implemented methods would be equal to the number of statuses multiplied by nuthe mber of business methods), but also badly designed (business logic for all states in single class). Also, an enum implementing an interface along with the rest of this fancy syntax may be counter intuitive to anyone who didn’t pass the SCJP exam in the last 2 weeks. Instead, we will provide a simple level of indirection because “Any problem in computer science can be solved with another layer of indirection”.
public enum ReservationStatus implements ReservationStatusOperations { NEW(new NewRso()), ACCEPTED(new AcceptedRso()), PAID(new PaidRso()), CANCELLED(new CancelledRso()); private final ReservationStatusOperations operations; ReservationStatus(ReservationStatusOperations operations) { this.operations = operations; } @Override public ReservationStatus accept(Reservation reservation) { return operations.accept(reservation); } @Override public ReservationStatus charge(Reservation reservation) { return operations.charge(reservation); } @Override public ReservationStatus cancel(Reservation reservation) { return operations.cancel(reservation); } }
This is the final source code for our ReservationStatus enum (implementing ReservationStatusOperations is not necessary). To put things simple: each enum value has its own distinct implementation of ReservationStatusOperations (Rso for short). This implementation is passed as a constructor argument and assigned to a final field named operations. Now, whenever a business method is called on enum, it will be delegated to the ReservationStatusOperations implementation dedicated to this enum:
ReservationStatus.NEW.accept(reservation); // will call NewRso.accept() ReservationStatus.ACCEPTED.accept(reservation); // will call AcceptedRso.accept()
The last piece of the puzzle is the Reservation domain object including business methods:
public void accept() { setStatus(status.accept(this)); } public void charge() { setStatus(status.charge(this)); } public void cancel() { setStatus(status.cancel(this)); } public void setStatus(ReservationStatus status) { if (status != null && status != this.status) { log.debug("Reservation#" + id + ": changing status from " + this.status + " to " + status); this.status = status; }
What happens here? When you call any business method on Reservation domain object instance, the corresponding method is being called on the ReservationStatus enum value. Depending on the current status, a different method (of different ReservationStatusOperations implementation) will be called. But there is no switch-case or if-else construct, only pure polymorphism. For example if you call charge() when the status field points to ReservationStatus.ACCEPTED, AcceptedRso.charge() is being called, the customer who made the reservation will be charged and reservation status changes to PAID.
But what happens if we call charge() again on the same instance? status field now points to ReservationStatus.PAID, so PaidRso.charge() will be executed, which throws a business exception (charging an already paid reservation is invalid). With no conditional code, we implemented a state-aware domain object with business methods included in the object itself.
One thing I haven’t mentioned yet is how to change the status from a business method. This is the second difference from the original GoF pattern. Instead of passing a StateContext instance to each state-aware operation (like accept() or charge()), which can be used to change the status, I simply return the new status from the business method. If the status is not null and it is different from the previous one (setStatus() method), the reservation transits to a given status. Let’s take a look at how it works on a AcceptedRso object (its methods are being executed when Reservation is in ReservationStatus.ACCEPTED status):
public class AcceptedRso implements ReservationStatusOperations { @Override public ReservationStatus accept(Reservation reservation) { throw new UnsupportedStatusTransitionException("accept", ReservationStatus.ACCEPTED); } @Override public ReservationStatus charge(Reservation reservation) { //charge client's credit card //send e-mail //print ticket return ReservationStatus.PAID; } @Override public ReservationStatus cancel(Reservation reservation) { //send cancellation e-mail return ReservationStatus.CANCELLED; } }
The behavior of Reservation in ACCEPTED status can easily be followed just by reading the class above: attempting to accept for the second time (when a reservation is already accepted) will throw an exception, charging will charge client’s credit card, print him a ticket and send e-mail etc. Also, charging returns PAID status, which will cause the Reservation to transit to this state. This means another call to charge() will be handled by different ReservationStatusOperations implementation (PaidRso) with no conditional code.
This would be all about the State pattern. If you are not convinced to this design pattern, compare the amount of work and how error-prone it is with classic approach using conditional code. Also think for a while what is needed when adding new state or state-dependent operation and how easy it is to read such a code.
I didn’t show all ReservationStatusOperations implementations, but if you would like to introduce this approach in your Spring or EJB based Java EE application, you have probably saw a big lie out there. I commented on what should happen in each business methods, but have not provided actual implementations. I didn’t, because I would come across a big problem: A Reservation instance is created by hand (using new) or by a persistence framework like Hibernate. It uses statically created enum which creates manually ReservationStatusOperations implementations. There is no way to inject any dependencies, DAOs and services, to this classes, as their life-cycle is controlled out of the Spring or EJB container scope. Actually, there is a simple yet powerful solution, using Spring and AspectJ. But be patient, I will explain it in details in the next post soon, adding some domain-driven flavor to our application.
That’s it. A very interesting post on how to leverage the state pattern within a DDD approach by our JCG partner, Tomasz Nurkiewicz. I am definitely looking forward to the next part of this tutorial which will be hosted at JavaCodeGeeks in a few days. UPDATE: The next part is Domain Driven Design with Spring and AspectJ.
Thank you for a great article.
I was looking a long time for a solution how to effectively handle state transitions.
Could you explain how you imagine to do this with spring framework and service/dao layers.
Spring statemachine does fit well in DDD paradigm. I wouldn’t go in AOP just for the state machine.