0

I have a problem with the structure of my API. My API includes the study domain class, which has an educationLevelId attribute; This attribute belongs to the academicLevel domain class.

Study Domain - Domain Layer

public class Study {
    private final Long id;
    private final Long employeeId;
    private Long educationLevelId;
    //Other attributes and methods
}

AcadmicLevel Domain - Domain Layer

public class educationLevel {
    private final Long id;
    //Other attributes and methods
}

public class AcademicLevel {
    private final Long id;
    private List<educationLevel> educationLevels;
    //Other attributes and methods
}

In my Study domain there is a findStudiesByEmployeeIdUseCase, which requires to return a StudyDTO.

Study Domain - Application layer

public class StudyDTO {
    private Long id;
    private Long employeeId;
    private Long educationLevelId;
    private Long academicLevelId;
    //Other attributes
}

StudyDTO requires the academicLevelId, which is an attribute of another domain class. To address this, I created the shared service FindAcademicLevelIdsByEducationLevelIdsService (interface) and its implementation is located in the application layer of the academicLevel domain.

Shared Domain - Application layer

public class AcademicAndEducationLevelIdDTO {
    private Long academicLevelId;
    private Long educationLevelId;
    //Getters
}

public interface FindAcademicLevelIdsByEducationLevelIdsService {
    List<AcademicAndEducationLevelIdDTO> execute(List<Long> educationLevelIds)
}

The problem is that the implementation uses the AcademicLevelRepository (an interface of domain layer), which shouldn't depend on the application layer according to Hexagonal Architecture principles. So I am not sure which approach to take.

Option 1:

<R> List<R> findAcademicLevelIdsBy(List<Long> educationLevelIds, Class<R> clazz)

In this option, the repository implementation would return a List of results according to the specified class type (Using entityManager). This way, the AcademicLevelRepository would not depend on the application layer.

Option 2:

In the second option, I would create a package in the domain layer containing all internal DTOs. The repository woulr return an internal DTO, which would then be mapped to AcademicAndEducationLevelIdDTO. I don't like this option because according to Hexagonal Architecture principles, the DTOs components belong to the application layer.


If there are other options, please tell me.

I need help with another options or helping on to take the better option.

1 Answer 1

0

So the first thing to check: is findStudiesByEmployeeIdUseCase a "query", which is to say, is it an essentially read only operation?

Because if that's the case, you might be better off bypassing the domain model altogether - instead, "just" call your data store with the appropriate arguments, and then map the results to the representation that you will use to return the data (aka your DTO).

"Aggregates" -- clusters of associated objects that we treat as a unit for the purpose of data changes -- might not be useful when... we aren't changing data. If you don't need an aggregate, then you don't need a repository (an object that can provide the illusion of an in memory collection of objects).

You'll usually want to have some form of facade, acting as an information barrier between the parts of code that know how to retrieve information and the parts of the code that know what to do with it; but that facade isn't necessarily an aggregate, and might not be something that you package with the domain code.


That said, if you did want to use your domain models (because that's the appropriate trade off to make in your context), then the usual answer would be to have a capability in your domain model that looks like:

List<SomeDomainValue> execute(List<Long> educationLevelIds)

and then also have, in your application code, a capability that looks like

DTO someDomainValueToDTO(SomeDomainValue domainValue)

and then those two things get composed

List<DTO> dtos = (
   (List<SomeDomainValue>) domainObjects
).map( ::someDomainValueToDTO )

In this design, the application code knows about the domain model, but not the other way around.


<R> List<R> findAcademicLevelIdsBy(List<Long> educationLevelIds, Class<R> clazz) 

That's... that's really close to another good alternative; Class<R> probably isn't what you want; because of the coupling that would introduce. But you can approximate the same idea via

<R> List<R> findAcademicLevelIdsBy(List<Long> educationLevelIds, Factory<R> someFactory) 

Where you essentially have the idea that there is some interface that is defined by the domain code, where that interface is defined to accept some domain values as arguments and return a generic thing; and then you implement that interface in the application code which knows that the thing to return is the appropriate DTO.

interface DomainModelToSomethingGeneric<R> {
  <R> R call(DomainValue domainValue);
}

And for this example, in your application code, you might have something that looks like

class DomainModelToDTO implements DomainModelToSomethingGeneric<DTO> {
  DTO call(DomainValue domainValue) {
    return someDomainValueToDTO(domainValue);
  }
}

In the general case, what you have is a form of "dependency inversion" - the domain model implements a protocol, and defines the collection of capabilities that it needs from the caller to execute that protocol, and the calling code provides an implementation of the required capabilities.

Of course, these indirections have costs, as well as benefits, so you need to evaluate them - in particular, is adding the extra layers of indirection going to make likely changes harder in the future?

We don't follow these patterns because they are a magical ritual that makes everything better; we follow the patterns because they prepare us well for some likely futures.

Good judgment comes from experience....

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.