mardi 21 mai 2019

Create a DAO with a mapping layer

I am trying to figure the best way to lay out this design and have been running into some issues.

I have a Dao interface, where R is the return Type and U is the database record impl:

public interface Dao<U extends UpdatableRecordImpl> {

  public List<U> insert(List<U> dbRecords);
  public List<U> fetchAll(Select<U> selectQuery);
}

I have a default Dao that implements Dao in the following way:

public class BasicDao<U extends UpdatableRecordImpl> implements Dao<U>{

  protected final DSLContext _dslContext;

  public BasicDao(Configuration configuration, T table) {
    _dslContext = DSL.using(configuration);
    _table = table;
  }

  /**
   * Fetch multiple records from database
   *
   * @param selectQuery input query
   * @return list of records representing the database records
   */
  public List<U> fetchAll(Select<U> selectQuery) {
    return selectQuery.fetchStream().collect(Collectors.toList());
  }

  /**
   * Persists db records
   * <p>
   * Performs a batch operation for more than one record
   *
   * @param dbRecords list of records that need to be persisted
   * @return list of records with updated with id's which were generated by database
   */
  public List<U> insert(List<U> dbRecords) {

    if (dbRecords.isEmpty()) {
      return ImmutableList.of();
    }
    // prepare insert statement
    Result<U> inserted = //insert records
    return inserted;
  }
}

Now what I'm trying to do is have a mapping layer on top of that. Such that we can have a class that will look like this:

  @Override
  public List<M> insert(List<M> mappedRecords) {

    // Map record templates to db records
    List<U> dbRecords = mapList(mappedRecords, this::map);

    //Map the result of inserting to DefaultDao
    return mapList(_defaultDao.insert(dbRecords), this::map);
  }

  @Override
  public List<M> fetchAll(Select<U> select) {
    return StreamEx.of(_defaultDao.fetchAll(select)).map(this::map).toList();
  }

  /**
   * Map list of POJOs' of one type to the list of POJOs' of another type
   *
   * @param records list of POJOs' that need to be mapped
   * @param mapper function to map a single POJO of one type to other POJO type
   * @param <P> Input POJO type
   * @param <B> Output POJO type
   * @return
   */
  protected <P, B> List<B> mapList(List<P> records, Function<P, B> mapper) {
    return StreamEx.of(records).map(mapper).toList();
  }

  /**
   * Transforms a representative record to it's database representation
   * @param mappedRecord mapped representation of database record
   * @return database record
   */
  protected U map(M mappedRecord) {
    return _mapper.map(mappedRecord);
  }

  /**
   * Maps database record to it's mapped representation
   * @param dbRecord database record
   * @return representation of the database record in M type
   */
  M map(U dbRecord) {
    return _mapper.inverse().map(dbRecord);
  }

I have tried adding a Decorator layer around BasicDao, but I run into an issue with the same closures (List<U> insert(List<U> dbRecords) and List<M> insert(List<M> dbRecords) have same closures).

What is the suggested way to work with this setup? I have found some hacky ways by making Dao take in two classes (return and recordImpl), but this looks hacky in the basic impl (implements Dao<U, U>) and breaks some existing functionality in classes that do generic class bounds (List<D extends BasicDao>).

Thanks for the help.

Aucun commentaire:

Enregistrer un commentaire