DTOs with different granularity

1.6k views Asked by At

I'm on a project that uses the latest Spring+Hibernate for persistence and for implementing a REST API. The different tables in the database contain lots of records which are in turn pretty big as well. So, I've created a lot of DAOs to retrieve different levels of detail and their accompanying DTOs.

For example, if I have some Employee table in the database that contains tons of information about each employee. And if I know that any client using my application would benefit greatly from retrieving different levels of detail of an Employee entity (instead of being bombarded by the entire entity every time), what I've been doing so far is something like this:

class EmployeeL1DetailsDto
{
    String id;
    String firstName;
    String lastName;
}

class EmployeeL2DetailsDto extends EmployeeL1DetailsDto
{
    Position position;
    Department department;
    PhoneNumber workPhoneNumber;
    Address workAddress;
}

class EmployeeL3DetailsDto extends EmployeeL2DetailsDto
{
    int yearsOfService;
    PhoneNumber homePhoneNumber;
    Address homeAddress;
    BidDecimal salary;
}

And So on...

Here you see that I've divided the Employee information into different levels of detail. The accompanying DAO would look something like this:

class EmployeeDao
{
    ...

    public List<EmployeeL1DetailsDto> getEmployeeL1Detail()
    {
        ...
        // uses a criteria-select query to retrieve only L1 columns
        return list;
    }

    public List<EmployeeL2DetailsDto> getEmployeeL2Detail()
    {
        ...
        // uses a criteria-select query to retrieve only L1+L2 columns
        return list;
    }

    public List<EmployeeL3DetailsDto> getEmployeeL3Detail()
    {
        ...
        // uses a criteria-select query to retrieve only L1+L2+L3 columns
        return list;
    }

    .
    .
    .
    // And so on
}

I've been using hibernate's aliasToBean() to auto-map the retrieved Entities into the DTOs. Still, I feel the amount of boiler-plate in the process as a whole (all the DTOs, DAO methods, URL parameters for the level of detail wanted, etc.) are a bit worrying and make me think there might be a cleaner approach to this.

So, my question is: Is there a better pattern to follow to retrieve different levels of detail from a persisted entity? I'm pretty new to Spring and Hibernate, so feel free to point anything that is considered basic knowledge that you think I'm not aware of.

Thanks!

4

There are 4 answers

1
Dragan Bozanovic On BEST ANSWER

I would go with as little different queries as possible. I would rather make associations lazy in my mappings, and then let them be initialized on demand with appropriate Hibernate fetch strategies.

I think that there is nothing wrong in having multiple different DTO classes per one business model entity, and that they often make the code more readable and maintainable.

However, if the number of DTO classes tends to explode, then I would make a balance between readability (maintainability) and performance.

For example, if a DTO field is not used in a context, I would leave it as null or fill it in anyway if that is really not expensive. Then if it is null, you could instruct your object marshaller to exclude null fields when producing REST service response (JSON, XML, etc) if it really bothers the service consumer. Or, if you are filling it in, then it's always welcome later when you add new features in the application and it starts being used in a context.

0
teemukin65 On

Another approach is to use only domain objects at Dao level, and define the needed subsets of information as DTO for each usage. Then convert the Employee entity to each DTO's using the Generic DTO converter, as I have used lately in my professional Spring activities. MIT-licenced module is available at Maven repository artifact dtoconverter . and further info and user guidance at author's Wiki:

http://ratamaa.fi/trac/dtoconverter

Quickest idea you get from the example page there:

Happy hunting...

0
Maze On

You will have to define in one way or another the different granularity versions. You can try to have subobjects that are not loaded/set to null (as recommended in other answers), but it can easily get quite awkward, since you will start to structure your data by security concerns and not by domain model. So doing it with individual classes is after all not such a bad approach.

You might want to have it more dynamic (maybe because you want to extend even your data model on db side with more data).

If that's the case you might want to move the definition out from code to some configurations (could even be dynamic at runtime). This will of course require a dynamic data model also on Java side, like using a hashmap (see here on how to do that). You gain thereby a dynamic data model, but loose the type safety (at least to a certain extend). In other languages that probably would feel natural but in Java it's less common.

It would now be up to your HQL to define on how you want to populate your object. The path you want to take depends now a lot on the context, how your object will get used

0
Christian Beikov On

Blaze-Persistence Entity Views have been created for exactly such a use case. You define the DTO structure as interface or abstract class and have mappings to your entity's attributes. When querying, you just pass in the class and the library will take care of generating an optimized query for the projection.

Here a quick example

@EntityView(Cat.class)
public interface CatView {
    @IdMapping("id")
    Integer getId();

    String getName();
}

CatView is the DTO definition and here comes the querying part

CriteriaBuilder<Cat> cb = criteriaBuilderFactory.create(entityManager, Cat.class);
cb.from(Cat.class, "theCat")
    .where("father").isNotNull()
    .where("mother").isNotNull();

EntityViewSetting<CatView, CriteriaBuilder<CatView>> setting = EntityViewSetting.create(CatView.class);
List<CatView> list = entityViewManager
                        .applySetting(setting, cb)
                        .getResultList();

Note that the essential part is that the EntityViewSetting has the CatView type which is applied onto an existing query. The generated JPQL/HQL is optimized for the CatView i.e. it only selects(and joins!) what it really needs.

SELECT
    theCat.id,
    theCat.name
FROM
    Cat theCat
WHERE theCat.father IS NOT NULL
  AND theCat.mother IS NOT NULL