10

I've been reading a lot about this stuff and I am currently in the middle of the development of a larger web-application and its corresponding back-end.

However, I've started with a design where I ask a Repository to fetch data from the database and map it into a DTO. Why DTO? Simply because until now basically everything was simple stuff and no more complexity was necessary. If it got a bit more complex then I started to map e.g. 1-to-n relations directly in the service layer. Something like:

// This is Service-Layer
public List<CarDTO> getCarsFromOwner(Long carOwnerId) {

    // Entering Repository-Layer
    List<CarDTO> cars = this.carRepository = this.carRepository.getCars(carOwnerId);
    Map<Long, List<WheelDTO>> wheelMap = this.wheelRepository.getWheels(carId);

    for(CarDTO car : cars) {
        List<WheelDTO> wheels = wheelMap.get(car.getId());
        car.setWheels(wheels);
    }

    return cars;
}

This works of course but it turns out that sometimes things are getting more complex than this and I'm starting to realize that the code might look quite ugly if I don't do anything about this.

Of course, I could load wheelMap in the CarRepository, do the wheel-mapping there and only return complete objects, but since SQL queries can sometimes look quite complex I don't want to fetch all cars and their wheels plus taking care of the mapping in getCars(Long ownerId).

I'm clearly missing a Business-Layer, right? But I'm simply not able to get my head around its best practice.

Let's assume I have Car and a Owner business-objects. Would my code look something like this:

// This is Service-Layer
public List<CarDTO> getCarsFromOwner(Long carOwnerId) {

    // The new Business-Layer
    CarOwner carOwner = new CarOwner(carOwnerId);
    List<Car> cars = carOwner.getAllCars();

    return cars;
}

which looks as simple as it can be, but what would happen on the inside? The question is aiming especially at CarOwner#getAllCars().

I imagine that this function would use Mappers and Repositories in order to load the data and that especially the relational mapping part is taken care of:

List<CarDTO> cars = this.carRepository = this.carRepository.getCars(carOwnerId);
Map<Long, List<WheelDTO>> wheelMap = this.wheelRepository.getWheels(carId);

for(CarDTO car : cars) {
    List<WheelDTO> wheels = wheelMap.get(car.getId());
    car.setWheels(wheels);
}

But how? Is the CarMapper providing functions getAllCarsWithWheels() and getAllCarsWithoutWheels()? This would also move the CarRepository and the WheelRepository into CarMapper but is this the right place for a repository?

I'd be happy if somebody could show me a good practical example for the code above.


Additional Information

I'm not using an ORM - instead I'm going with jOOQ. It's essentially just a type-safe way to write SQL (and it makes quite fun using it btw).

Here is an example how that looks like:

public List<CompanyDTO> getCompanies(Long adminId) {

    LOGGER.debug("Loading companies for user ..");

    Table<?> companyEmployee = this.ctx.select(COMPANY_EMPLOYEE.COMPANY_ID)
        .from(COMPANY_EMPLOYEE)
        .where(COMPANY_EMPLOYEE.ADMIN_ID.eq(adminId))
        .asTable("companyEmployee");

    List<CompanyDTO> fetchInto = this.ctx.select(COMPANY.ID, COMPANY.NAME)
        .from(COMPANY)
        .join(companyEmployee)
            .on(companyEmployee.field(COMPANY_EMPLOYEE.COMPANY_ID).eq(COMPANY.ID))
            .fetchInto(CompanyDTO.class);

    return fetchInto;
}
Stefan Falk
  • 23,898
  • 50
  • 191
  • 378
  • what framework are you useing for your Model relations? JPA? Hibernate? – Tschallacka Aug 24 '16 at 16:39
  • @MichaelDibbets Not sure what you mean by framework but I'm not using a ORM. I'm using jOOQ for accessing the database. – Stefan Falk Aug 24 '16 at 16:40
  • http://www.jooq.org/doc/3.1/manual/sql-execution/logging/ Have a look there what kind of queries are built up. Then you can see if only related cars are fetched, or if ALL cars are fetched. – Tschallacka Aug 24 '16 at 16:42
  • @MichaelDibbets Not sure if I understand. I'm completely aware about what's happening while I use jOOQ. My main issue is the part that comes *after* the repository layer. As explained above, my problem is that I am not sure where I am resolving the relations of the data which I'm getting from the Repository-Layer. – Stefan Falk Aug 24 '16 at 16:47
  • I'd imagine in a "business layer" you'd load values from database into business objects. Most orm frameworks do this by reflection. but if you know the layout, you can do it manually too. I'll post a small example of what I mean, but not sure if that is what you are looking for. – Tschallacka Aug 24 '16 at 16:52
  • Consider design patterns for example **CQRS** http://martinfowler.com/bliki/CQRS.html ; Consider separation of concerns; Consider testing your functionality using Unit Tests; It is just not about data, but how we operate on them. – L J Sep 16 '16 at 23:16

2 Answers2

1

Pattern Repository belongs to the group of patterns for data access objects and usually means an abstraction of storage for objects of the same type. Think of Java collection that you can use to store your objects - which methods does it have? How it operates?

By this defintion, Repository cannot work with DTOs - it's a storage of domain entities. If you have only DTOs, then you need more generic DAO or, probably, CQRS pattern. It is common to have separate interface and implementation of a Repository, as it's done, for example, in Spring Data (it generates implementation automatically, so you have only to specify the interface, probably, inheriting basic CRUD operations from common superinterface CrudRepository). Example:

class Car {
   private long ownerId;
   private List<Wheel> wheels;
}

@Repository 
interface CarRepository extends CrudRepository<Car,Long> {
   List<Car> findByOwnerId(long id);
}

Things get complicated, when your domain model is a tree of objects and you store them in relational database. By definition of this problem you need an ORM. Every piece of code that loads relational content into object model is an ORM, so your repository will have an ORM as an implementation. Typically, JPA ORMs do the wiring of objects behind the scene, simpler solutions like custom mappers based on JOOQ or plain JDBC have to do it manually. There's no silver bullet that will solve all ORM problems efficiently and correctly: if you have chosen to write custom mapping, it's still better to keep the wiring inside the repository, so business layer (services) will operate with true object models. In your example, CarRepository knows about Cars. Car knows about Wheels, so CarRepository already has transitive dependency on Wheels. In CarRepository#findByOwnerId() method you can either fetch the Wheels for a Car directly in the same query by adding a join, or delegate this task to WheelRepository and then only do the wiring. User of this method will receive fully-initialized object tree. Example:

class CarRepositoryImpl implements CarRepository {

  public List<Car> findByOwnerId(long id) {
     // pseudocode for some database query interface
     String sql = select(CARS).join(WHEELS); 
     final Map<Long, Car> carIndex = new HashMap<>();
     execute(sql, record -> { 
          long carId = record.get(CAR_ID);
          Car car = carIndex.putIfAbsent(carId, Car::new);
          ... // map the car if necessary
          Wheel wheel = ...; // map the wheel
          car.addWheel(wheel);
     }); 
     return carIndex.values().stream().collect(toList());
  }
}

What's the role of business layer (sometimes also called service layer)? Business layer performs business-specific operations on objects and, if these operations are required to be atomic, manages transactions. Basically, it knows, when to signal transaction start, transaction commit and rollback, but has no underlying knowledge about what these messages will actually trigger in transaction manager implementation. From business layer perspective, there are only operations on objects, boundaries and isolation of transactions and nothing else. It does not have to be aware of mappers or whatever sits behind Repository interface.

Ivan Gammel
  • 652
  • 7
  • 17
  • Well, what you're saying makes sense but my problem is that I'm not so sure if this is scaling so great using such a `CarRepositoryImpl`. Let's say I do it that way but I have a `CarDealerDTO` that is supposed to consist of the car-dealer information as well as a list of the cars being available. In you scenario this would make `CarDealerRepository` responsible for all that. It would have to fetch the car-dealer information and all the cars with all their wheels. What I just don't get around my head here is where to draw the line and how to shift this mapping responsibility elsewhere. – Stefan Falk Sep 18 '16 at 11:49
  • You can use the difference between composition and aggregation for that purpose. Entity A is composed of entity B if lifecycle of B is within lifecycle of A (controlled by A). Entity A aggregates entity B if their lifecycles are independent. Car is composed of wheels, so CarRepository can fetch wheels. Car dealer aggregates cars (vintage car can be manufactured long before dealership business started and selling a car does not mean, it is destroyed), so CarDealerRepository fetches only dealership info and CarRepository finds Cars by dealer id. – Ivan Gammel Sep 18 '16 at 12:58
  • Also, you can control the state of object tree by using projections (or, sometimes, they are called "expansions"). Projection is a subinterface of domain object, that provides access to a limited subset of its attributes. It's a bit of overhead in simple cases, but it's useful when you have two or three different frequent use cases and want more explicit alternative to lazy loading. Example: class WheeledCar implements Car { ... }, class CarRepository { WheeledCar findCarWithWheels(...) { WheeledCar car = doFind(..); car.setWheels(...); return car; } Car findCar(...) { return doFind(..); } – Ivan Gammel Sep 18 '16 at 13:05
0

In my opinion, there is no correct answer. This really depends upon the chosen design decision, which itself depends upon your stack and your/team's comfort.

Case 1:

I disagree with below highlighted sections in your statement:

"Of course, I could load wheelMap in the CarRepository, do the wheel-mapping there and only return complete objects, but since SQL queries can sometimes look quite complex I don't want to fetch all cars and their wheels plus taking care of the mapping in getCars(Long ownerId)"

sql join for the above will be simple. Additionally, it might be faster as databases are optimized for joins and fetching data. Now, i called this approach as Case1 because this could be followed if you decide to pull data via your repository using sql joins. Instead of just using sql for simple CRUD and then manipulating objects in java. (below)

Case2: Repository is just used to fetch data for "each" domain object which corresponds to "one" table.

In this case what you are doing is already correct. If you will never use WheelDTO separately, then there is no need to make a separate service for it. You can prepare everything in the Car service. But, if you need WheelDTO separately, then make different services for each. In this case, there can be a helper layer on top of service layer to perform object creation. I do not suggest implementing an orm from scratch by making repositories and loading all joins for every repo (use hibernate or mybatis directly instead)

Again IMHO, whichever approach you take out of the above, the service or business layers just complement it. So, there is no hard and fast rule, try to be flexible according to your requirements. If you decide to use ORM, some of the above will again change.

Stacky
  • 503
  • 2
  • 6
  • 23