Last month I wrote about moving OfficeFloor v4 from one graphical file to YAML per endpoint. Spring PetClinic REST was the worked example. The configuration story was right. The code story was not.
That conversion gave each endpoint its own YAML file. But behind most of those files sat a single function. It did everything the old controller method did. Validate. Look up. Mutate. Save. Respond. I had swapped the configuration format and left the fat method alone. Explicit orchestration over one function is just a verbose @GetMapping.
So I did the conversion again. Properly. The code is up as a pull request with the following walk through:
What changed
The controllers are gone. All nine of them. So are ExceptionControllerAdvice and BindingErrorsResponse. In their place are 37 endpoint YAML files and 73 small function classes.
An update endpoint now reads as the steps it always was.
# rest/api/owners/{ownerId}.PUT.yml
composition:
authorize: "hasRole('OWNER_ADMIN')"
validate:
class: ...rest.function.owner.ValidateOwner
govern: [ transaction ]
next: load
load:
class: ...rest.function.owner.LoadOwner
govern: [ transaction ]
next: apply
apply:
class: ...rest.function.owner.ApplyOwner
govern: [ transaction ]
next: save
save:
class: ...rest.function.owner.SaveOwner
govern: [ transaction ]
next: respond
respond:
class: ...rest.function.owner.RespondWithOwnerUpdated
govern: [ transaction ]
Validate. Load. Apply. Save. Respond. You do not reconstruct the flow by reading a method body.
The functions stay small enough to hold in your head.
public class LoadOwner {
public void service(@PathVariable(name = "ownerId") Integer ownerId,
OwnerRepository ownerRepository, Out<Owner> loaded) throws NotFoundException {
loaded.set(Lookups.findOrNotFound(() -> ownerRepository.findById(ownerId),
"Owner not found: " + ownerId));
}
}
State moves between steps as variables. Out<Owner> here. @Val Owner in the function that consumes it. The steps stay decoupled. Each one is unit testable on its own.
The parts that stopped being code
Two cross cutting concerns fell out of the Java entirely.
Transactions are govern: [ transaction ] on the steps that need them. The transaction spans the orchestration. Not a method boundary. That is what you wanted from @Transactional anyway.
Exception handling is a directory. The file escalation/...NotFoundException.yml names the handler for NotFoundException. An escalation and its handler are now discoverable by file name.
Security stayed Spring's. The authorize: "hasRole('OWNER_ADMIN')" line is a Spring expression. The repositories, mappers, DTOs and Spring Boot wiring are untouched. This is not a rewrite away from Spring.
It is Spring with the request flow made explicit.
Was it worth it
There are more files. 73 classes where there were nine controllers. But each one is short. Each is independently testable. Each is named after what it does. And the file that composes them tells you the whole route at a glance.
If you looked at the last conversion and thought the YAML was not buying you much, that was fair. Have another look at this one.
No comments:
Post a Comment