Builder Pattern – Is the Builder Pattern Appropriate for Updating Objects in a Service Layer?

Architecturedesigndesign-patternsjava

Currently, in our service layer, we pass an id as well as a new updated value, something akin to

updatePersonName(Person person, Name name)

which, in turn, calls the corresponding repository functions.
Now this works fine if we want to update only a single value. But once we start wanting to update multiple values at the same time, we're either stuck calling various service methods in a row or defining a service method that takes multiple arguments to update.
It is bearable, but it gets even worse when we need to update multiple values that are forced to be updated together, which means we are forced to define a (maybe new) method in the service that makes sure the combination is met.
And if you start having some limitations (perhaps a Person that's marked as non-updatable due to different reasons), it just increases the complexity even further.

Recently I've been thinking of using the builder pattern, but not for object creation, but updating. Something like this (all the methods chosen completely arbitrary, but you get the point):

PersonService.updater(person)
    .setName("newName")
    .addRole(newRoleObject)
    .setFlag(PersonFlag.NOT_UNDERAGE)
    .overrideBlock()
    .withEventDescription("Person changed her name on birthday!")
    .update();

The builder could internally resolve the logic without exposing too much complexity to the outside. The fluent API is easy to use in every other service/component that needs access. I don't need to create a plethora of methods to cover whatever requirement came up. Multiple updates are easy to chain together, and if you want to update something that's now allowed, it could block that internally unless you override said block. And it would be able to force specific fields due to type safety, for example, an EventObject.

More importantly, this could also, in turn, make sure we only make a single trip to the repository, instead of multiple. It would improve runtime, especially in critical algorithms that require many passes to the database otherwise.

I can see a few issues with this approach, as well. It's bulky, is an unconventional API for people not experienced with it, and could lead to some misuse. Implementing it is not trivial while making sure the internal logic keeps together. However, I think the positives outweigh the negatives in my situation.

Am I missing something?

Edit: I know that usually, you have a save() function inside of your DTO that takes care of saving, but due to existing infrastructure, that isn't an option at this point.

Best Answer

First of all this is not the GoF Builder Pattern. This might be the Joshua Bloch Builder Pattern. It's used to simulate named arguments in languages (like Java) that don't have them.

Named arguments make long argument lists tolerable by clearly labeling the arguments. They also enable optional arguments so the list doesn't have to include every possible argument the way positional arguments do.

If that's all you're doing then you really are doing object construction because that's what the Joshua Bloch Builder gives you, an immutable object with all those fields set.

If that's not what you're doing then this is an internal or embedded Domain Specific Language (DSL). These have the power to control what does and doesn't come next. They do that because the methods return different types that enable new methods.

This provides you with a LOT of power and enough rope to hang yourself. It's also a lot of work to set up behind the scenes. But it does have good uses. For example JOOQ and Java 8 streams. I've successfully used it to formalize construction of a nightmarish god object that was also used to update the database and was too entrenched to reform.

The key thing with this is the business rules are enforced by the mini language you're building. That's great but it sets them in stone. DSLs are not easy to write and they are not easy to change. Use them when they will be used a LOT and changed very little.

Now that said you can avoid hard coding the implementation with a little dependency injection.

happyNewName(PersionService personService, Person person, Role newRoleObject) {
    personService
        .updater(person)
        .setName("newName")
        .addRole(newRoleObject)
        .setFlag(PersonFlag.NOT_UNDERAGE)
        .overrideBlock()
        .withEventDescription("Person changed her name on birthday!")
        .update()
    ;
}

Done this way the DSLs 'source' and it's implementation can be changed independently.

This lets you create facades that guide coders to follow complex ceremonies that might be necessary. However, often they are not really necessary if you'll just do the work to simplify the system. The danger here is if you do a DSL instead you'll be digging this technical debt hole even deeper. So be sure you really have no better solution, because this is the nuclear option.

This fluent style is trending right now but be advised that you need to design your DSL from end-to-end. No sneaky picking up random classes in the middle of this. That's a huge Law of Demeter violation. Only dot through classes designed to work together like this. Not random ones that happen to be lying about the place.