I find Value Object pattern really satisfying. Lots of books like Secure by Design, Implementing Domain-Driven Design, and Domain Modeling Made Functional advocate to apply Value Object to make domain types clearer and more type safe.
For example, suppose I have Order
entity that contains name. I could mark the name
field as String
but Value Object pattern suggests me to create a separate class OrderName
. And the constructor may contain validations for the name of an Order
. Look at the code example below.
@Entity
class Order {
...
@Embedded
private OrderName name;
}
@Data
@Setter(PRIVATE)
@Embeddedable
class OrderName extends SelfValidated {
// custom bean validation annotation
@OrderNameValid
private String value;
public OrderName(String value) {
// name should be no longer than 50 characters
this.value = value;
// call bean validator manually
validateSelf();
}
protected OrderName() {
// called by Hibernate
// Hibernate automatically scans bean validation annotaions
// and invokes validator each time value object is persisted/loaded
}
}
At first glance, everything looks good. I cannot create OrderName
instance, if the passed input violates stated business rules (in this case, max length is 50 characters). But what if requirements change? Suppose that business decides that an order name max length should be 30 characters. It means that I cannot work with old orders. Because I have to read the row from the database and create the Order
entity, if I need to alter it somehow.
I've been thinking about this problem for a long time. I can think about 4 solutions but I don't consider neither of them as completely valid. So, I wonder do you have any thoughts about it? I'd be glad to hear your opinions. Anyway, here are my ways to overcome the issue.
This one is straightforward. If data is not valid, change it so it becomes valid. Developers tend to add such updates as Flyway or Liquibase migrations. This can work under particular circumstances. Anyway, the solution is far from perfect. Here is why:
I came up with this solution by myself and even gave a talk at the conference about it. The idea is simple. Look at the code example below.
@Entity
class Order {
...
private String name;
// pass value object to update Order state
public void changeName(OrderName name) {
// unwrap it to store raw value
this.name = name.getValue();
}
}
As you can see, Hibernate entity stores name
as a raw String
type. But if we want to change it, then public method accepts OrderName
Value Object which is unwrapped afterward. When you read data from the database, Hibernate creates an entity instance with no-args constructor and sets values to fields via Java Reflection API. It gives us several opportunities:
However, this approach still have some drawbacks:
Order
method that needs its name for further operations, you cannot construct value object from it. Because if you do, you may get an exception due to invalidity of old data. So, even inside domain level you still work with old dataIt seems like here Value Object's usage is not self-explanatory. And that's why comes the final option.
In this case, Value Objects are validated only if you create them in your code directly. Otherwise, if Hibernate instantiates Value Object with Java Reflection API, then the value is not validated and just being set as-is. Look at the code example below:
@Data
@Setter(PRIVATE)
@Embeddedable
class OrderName {
private String value;
public OrderName(String value) {
// name should be no longer than 50 characters
this.value = validateValue(value);
}
protected OrderName() {
// called by Hibernate
// No validations happens here
}
}
The problem of backward compatibility is no longer an issue. However, the idea of Value Object is broken. The whole point of Value Object is that it cannot be instantiated with invalid data. Though now it's not the case. Because Hibernate can create OrderName
with invalid value
by calling protected
constructor.
If you apply such solution, then your code becomes not so resilient. Look at the code example below:
@Entity
class Order {
...
private String name;
public void changeName(OrderName name) {
// is it a valid object?
this.name = name;
}
}
What if passed OrderName
has been created by Hibernate but not by client. In such scenario, I cannot guarantee that OrderName
corresponds with the current validation rules. So, the code transforms to something like this:
@Entity
class Order {
...
private String name;
public void changeName(OrderName name) {
if (name.isValid()) {
this.name = name;
} else {
throw new OrderNameNotValidException(...);
}
}
}
If I allow a Value Object to be either valid or invalid, I get no benefits of using it. Besides, I even make code harder and less straightforward.
So, if Value Object pattern brings us so many obstacles, maybe we just need to throw it away? Besides, some IT experts like Allen Holub claim that Value Object is an anti-pattern as the entire idea. Maybe it is, not sure about that.
I'll be glad to read your opinions about this problem. Especially if you're Hibernate user just like me. I'd really appreciate any valuable advice.
I would draw attention to the concept of the correctness of ValueObjects. In my opinion, a ValueObject created from old data stored in a database is considered correct. If database migration is not possible due to business requirements, it is logical to assume that business requirements specify behavior for previously created objects through business rules, such as requiring the user to manually change the name before performing operations on the object; operations on objects created before the application of new business requirements follow a separate logic path from the one followed by objects created after the appearance of the business requirements. In this case, there are no contradictions in implementing the ValueObject, but unfortunately, it will not protect it from increasing complexity - which in this case is natural.