Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Please explain the Repository-, Mapping-, and Business-Layer relations and responsibilities

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;
}
like image 992
Stefan Falk Avatar asked Aug 24 '16 16:08

Stefan Falk


People also ask

What is repository mapping?

The Mapping Repository launches the Source Program Mapping Repository editor dialog. The editor provides an interface to manage files/directories of source program listings used for the source program mapping display feature of individual reports. The repository is segmented into two lists: Libraries and Directories.

What is the responsibility of repository?

Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.

Is repository a business layer?

The repository acts as a mediator between the data source and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source.

Is repository part of data layer?

The data layer is made of repositories that each can contain zero to many data sources. You should create a repository class for each different type of data you handle in your app.


1 Answers

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.

like image 126
Ivan Gammel Avatar answered Sep 28 '22 10:09

Ivan Gammel