Steven
Steven

Reputation: 461

ArchUnit: Verify method only calls one outside method

In a Controller-Service-Datalayer architecture, I'm searching for a way to verify that my controller methods perform exactly one call to the service layer like this:

@DeleteMapping(value = "/{id}")
public ResponseEntity<String> deleteBlubber(@PathVariable("id") long blubberId) {

    service.deleteBlubber(blubberId);

    return new ResponseEntity<>("ok", HttpStatus.OK);
}

This should not be allowed:

@DeleteMapping(value = "/{id}")
public ResponseEntity<String> deleteBlubber(@PathVariable("id") long blubberId) {

    service.deleteOtherStuffFirst();   // Opens first transaction
    service.deleteBlubber(blubberId);  // Opens second transaction - DANGER!

    return new ResponseEntity<>("ok", HttpStatus.OK);
}

As you can see from the comments, the reason for this is to make sure that each request is handled in one transaction (that is started in the service layer), not multiple transactions.

It seems that ArchUnit can only check meta-data from classes and methods and not what's actually going on in a method. I would have to be able to count the request to the service classes, which seems to not be possible in ArchUnit.

Any idea if this might be possible? Thanks!

Upvotes: 3

Views: 753

Answers (1)

Roland Weisleder
Roland Weisleder

Reputation: 10511

With JavaMethod.getMethodCallsFromSelf() you have access to all methods calls of a given method. This could be used inside a custom ArchCondition like this:

methods()
    .that().areDeclaredInClassesThat().areAnnotatedWith(Controller.class)
    .should(new ArchCondition<JavaMethod>("call exactly one service method") {
        @Override
        public void check(JavaMethod item, ConditionEvents events) {
            List<JavaMethodCall> serviceCalls = item.getMethodCallsFromSelf().stream()
                    .filter(call -> call.getTargetOwner().isAnnotatedWith(Service.class))
                    .toList();

            if (serviceCalls.size() != 1) {
                String message = serviceCalls.stream().map(JavaMethodCall::getDescription).collect(joining(" and "));
                events.add(SimpleConditionEvent.violated(item, message));
            }
        }
    })

Upvotes: 4

Related Questions