Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ArchUnit: Verify method only calls one outside method

Tags:

archunit

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!

like image 634
Steven Avatar asked Jan 23 '26 04:01

Steven


1 Answers

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));
            }
        }
    })

like image 51
Roland Weisleder Avatar answered Jan 26 '26 04:01

Roland Weisleder