Why does this ArchUnit test fail?

418 Views Asked by At

I have a demo project which tries to respect strictly clean/onion/hexagonal architecture. Here is how I configure ArchUnit test :

    @AnalyzeClasses(packages ="fr.tristan.demoassurance", importOptions = {ImportOption.DoNotIncludeTests.class, ExcludeConfig.class})
public class ArchitectureTest {

    @ArchTest
    public static final ArchRule onionArchitectureRule = onionArchitecture()
        .domainModels("fr.tristan.demoassurance.domain.entity..")
        .domainModels("fr.tristan.demoassurance.domain.event..")
        .domainServices("fr.tristan.demoassurance.domain.api..")
        .applicationServices("fr.tristan.demoassurance.application.controller")
        .adapter("message", "fr.tristan.demoassurance.infrastructure.message..")
        .adapter("persistence-mongodb", "fr.tristan.demoassurance.infrastructure.repository.mongodb..")
        .adapter("persistence-mysql", "fr.tristan.demoassurance.infrastructure.repository.mysql..")
        ;
}

ArchUnit complains about one parameter :

was violated (1 times):
Method <fr.tristan.demoassurance.domain.spi.message.MessageQueueSpi.send(fr.tristan.demoassurance.domain.event.PoliceAssuranceEvent)> has parameter of type <fr.tristan.demoassurance.domain.event.PoliceAssuranceEvent> in (MessageQueueSpi.java:0)

Here is the "guilty" method :

package fr.tristan.demoassurance.domain.spi.message;

import fr.tristan.demoassurance.domain.event.PoliceAssuranceEvent;

public interface MessageQueueSpi {
    void send(PoliceAssuranceEvent policeAssuranceCreatedEvent);
}

This makes no sense for me because :

  1. I don't see which rule is violated, it's just not explained why a SPI interface should not have a parameter of a domain model type.

  2. I have an other interface in the same fr.tristan.demoassurance.domain.spi parent package with a savemethod which also have a parameter PoliceAssurance from "domain models" and ArchUnit doesn't complain about it :

package fr.tristan.demoassurance.domain.spi.repository;

import java.util.List;

import fr.tristan.demoassurance.domain.entity.PoliceAssurance;
import fr.tristan.demoassurance.domain.exception.PoliceAssuranceException;

public interface PoliceAssuranceRepositorySpi {

    List<PoliceAssurance> findAll();
    PoliceAssurance save(PoliceAssurance policeAssurance) throws PoliceAssuranceException;
    PoliceAssurance findById(Integer id) throws PoliceAssuranceException;
    void deleteById(Integer id) throws PoliceAssuranceException;    
}
2

There are 2 best solutions below

0
Tristan On

I made it work by :

  1. putting domain models in the same "domainModels(...)"
  2. adding mappers and spi interfaces to respectively "applicationServices(...)" and "domainServices(...)"
@AnalyzeClasses(packages ="fr.tristan.demoassurance", importOptions = {ImportOption.DoNotIncludeTests.class, ExcludeConfig.class, ExcludeTestFactory.class})
public class ArchitectureTest {


    @ArchTest
    public static final ArchRule onionArchitectureRule = onionArchitecture()
        .domainModels("fr.tristan.demoassurance.domain.entity..", "fr.tristan.demoassurance.domain.event..")
        .domainServices("fr.tristan.demoassurance.domain.api..", "fr.tristan.demoassurance.domain.spi..")
        .applicationServices("fr.tristan.demoassurance.application.controller..", "fr.tristan.demoassurance.application.mapper..")
        .adapter("message", "fr.tristan.demoassurance.infrastructure.message..")
        .adapter("persistence-mongodb", "fr.tristan.demoassurance.infrastructure.repository.mongodb..")
        .adapter("persistence-mysql", "fr.tristan.demoassurance.infrastructure.repository.mysql..")
        ;
}
3
Manfred On

As you found out, domainModels behaves like a setter, i.e. does not extend the internal domainModelPredicate (and likewiese for domainServices and applicationServices). That is, when calling the methods multiple times, previous package identifiers are simply overwritten.