Keeping sane in the depth of madness
This last year I’ve been working on systems that integrate with a master ERP-system over SOAP. As with all thing enterprise, things tends to get a bit messy.
Most interactions with the SOAP-interface requires the user to specify a lot of identifactors for other objects. In our system, most of these are represented as Long
-instances.
A naive implementation of this does not a happy programmer make.
A typical service for talking through the SOAP may look a bit like this.
public void triggerAccountAgreementUpdate(
Long account,
Long agreement,
Long userTypeId,
Long addressId,
Long updateTypeId, ....) {}
On the surface, that might not be so bad, but give it some time, a couple of more parameters that suddenly are needed, and the typical call of this methods looks something like this:
srvc.triggerAccountAgreementUpdate(
12313243L,
14223421L,
2L,
234251L,
1065L, ...)
This is starting to be hard to read. Some of the readability issues can be solved with declaring all the paramteres as properly named variables. One more problem that appears is when you have multiple ids of entities that look like each other. One example of this that I’ve found is where you have different types of addresses that you work with. If the type is only encoded in the names, these can be mistaken for each other, creating weird bugs that are hard to track down.
Given that this is in a statically typed (though with a bit broken type system) programming language, we should be able to leverage types to guide us.
In languages such as Scala and Go, this can easily be done via type aliasing
type CustomerId int32
type AddressId int32
In Java, we have no such luck, and must go through the usual boilerplate hell.
public final class CustomerId {
public final Long id;
public CustomerId(final Long id) {
this.id = checkNotNull(id);
}
// .equals and .hashCode below
}
By creating these types we can be a bit more explicit in our methods, and give the programmer a bit more information to work with.
public void triggerAccountAgreementUpdate(
AccountId account,
AgreementId agreement,
UserTypeId userTypeId,
AddressId addressId,
UpdateType updateTypeId, ....) {}
The overhead of doing this might be discouraging, but we’ve had great results by using this technique. I think this becomes more and more important as the complexity of the application grows. Given a n-layered stack where the data has to flow through a wide range of methods and indirections before actually being used, then it is crucial not to mess this stuff up.
For more examples of this technique and others take a look at Jessica Kerrs excellent talk from Øredev this year.
Additional note
Since REST and JSON is all the rage these days, here is a small tip for using these identifier types using jackson.
Given the entity Customer
defined like this:
public class Customer {
public final CustomerId id;
public final String name;
}
then the default serialization would produce something like this:
{"id": {"id": 1234}, "name": "Alice Example"}
Thats a bit silly.
We can fix this by using the @JsonUnwrapped
-annotation provided by Jackson.
public class Customer {
@JsonUnwrapped
public final CustomerId id;
public final String name;
}
If you use this annotation, then other annotations thats should affect the field, must be put in the CustomerId-class, not in the Customer-class.
For using the field as a *Param
in Jersey (JAX-RS) the id-classes must obey the rules given in https://jersey.java.net/documentation/latest/jaxrs-resources.html#d0e1811.
TL;DR of these are provide a constructor that takes a single string-argument for constructing the entity, or have a static method valueOf(string)
that can create new instances.