Having irrelevant extra parameters and DTO to impelememt IRepository in Domain layer

811 Views Asked by At

I have a solution that I have implemented by Onion architecture and DDD like eShopOnContainers.

But I have an issue to do this, so I decided share it with you. I try to explain it by an example. You suppose I have a interface that named IOrderRepository like IOrderRepository

public interface IOrderRepository : IRepository<Order>
    {
        Order Add(Order order);               // first issue
        Task<OrderDTO> GetAsync(int orderId); // second issue
    }

First issue

I implemented Add method like OrderRepository but I need extra parameters in Add method like following code:

public Order Add(Order order, int par1, int par2)
    {
        // I need a DTO to persist order  
        OrderDTO orderDTO = new OrderDTO({Order = order, Par1 = par1, Par2 = par2 });

        // Call a method with orderDTO parameter as a service to insert order             
    }

As you can see, implementing IOrderRepository is wrong because of extra parameters that I need.

Wrong solutions for first issue

I have two wrong solutions to tackle with this issue.

1- Adjusting IOrderRepository

Changing input IOrderRepositoryparameters by adding the parameters like this:

public interface IOrderRepository : IRepository<Order>
    {
        Order Add(Order order, int par1, int par2);           
    }

As I know, there isn't any business rule for par1 and par2 and to do implement DDD, first of all I should specify IRepository, but by using this solution, I put Infrastructure layer concerns in Domain layer that is wrong architecture.

2- Take IOrderRepository to Infrastructure layer

I can put IOrderRepository in Infrastructure layer, but it's another wrong architecture because as far as I know this sort of interfaces should be located in Domain layer.

My first question

1- How I can use extra parameters in methods of a Repository in Infrastructure layer to implement IRepository of Domain layer that there isn't any connection between the parameters and Domain layer?

Second issue

As you can see in IOrderRepository, I should implement GetAsync method that returns OrderDTO including Order and extra parameters. As I know, I can't use DTO (Data Transfer Object) in Domain layer. I couldn't come up with an idea to handle it.

My second question

2- How I can return DTO OrderRepository methods that is in infrastructure layer, but I can't apply it in IOrderRepository that is in Domain layer.

Thanks in advance for your time.

3

There are 3 best solutions below

7
Eben Roux On

Since we have no context as to why you need those two parameters you could also go with two other, probably less intrusive, options:-

Firtly, you could have a new class that inherits from Order and also contains those extra parameters:

public class MyOrder : Order
{ 
    int Par1 { get; set; }
    int Par2 { get; set; }
}

In your repository you could then cast, or safe-cast, to the MyOrder class.

Another option would be to use an extension method for IOrderRepository:

public static class SomeExtension
{
    public static void MySave(this IOrderRepository repository, int par1, int par2)
    {
        // do things
    }
}

Perhaps these would not work in your scenario so YMMV. In either case you may need to take another look at your design.

2
Mathieson On

What is in the parameters?

If it's part of the Order, your model should reflect that. Having to take in supposedly unrelated information is usually a very clear sign that your object model doesn't reflect reality. Which is the whole point of DDD.

If they're other things that will be consistent across the life of the repository, give them as parameters to the Repository on its construction (dependency injection). It can then use these internally to store the item.

Here's an example of the latter - let's say I have an eCommerce company, and we have a legacy order system that needs to be notified on older orders, but in new orders it's no longer needed. I can encapsulate this behavior in my factory like (C# example):

public class OrderRepository : IOrderRepository
{
     private LegacyOrderService _legacyService;
     public OrderRepository(LegacyOrderService legacyService){
          _legacyService = legacyService;
     }

     public Order Add(Order order){
         if(!order.isNewOrder){
              _legacyService.notify(order.id);
         }

         # do the rest
     }
}
3
johnny 5 On

This looks like an X-Y Problem, There are serveral issues with your code and you are not explaining the problem well enough, your just explaining what you want as an answer.

If you look at the Code you've linked to this is their implementation of IRepository (I've omitted irrelevant methods to answer the question)

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order); 
    Task<Order> GetAsync(int orderId);
}

Here you can see that they cleared are only Operating on the Order and not the DTO Yet in the implementation you've provided you return the DTO, which would be impossible to return without link a separate parameter

public interface IOrderRepository : IRepository<Order>
{
    Order Add(Order order);               
    Task<OrderDTO> GetAsync(int orderId); 
}  

You probably want a more complex architecture but there won't be enough space to layout the full code so I'll just give you a Gyst Take in both Parameters Order and OrderDTO, and add infrastructure for handling adaptations

public interface IOrderRepository : IRepository<OrderDTO>
{
    OrderDTO Add(OrderDTO order); 
    Task<OrderDTO> GetAsync(int orderId);
}


public interface IAdaptable<TEntity, TDTO>
{
    TDTO ToDTO(TEntity entity);
    TEntity ToEntity(TDTO entityDTO, TEntity entity);
}

Then you'll need a base class for handling the operations

public class BaseRepository<TEntity, TDTO> : IRepository<TEntity, TDTO>
    where TEntity : class, new()
{
     protected DbContext _context;
     protected IAdaptable<TEntity, TDTO> _adapter;
     public BaseRepository(DbContext context, IAdaptable<TEntity, TDTO> adapter)
     {
         this._context = context;
         this._adapter = adapter;
     }

     public TDTO Add(TDTO dto)
     {
        var entity = this._adapter.ToEntity(dto, new TEntity());
        this._context.Set<TEntity>().Add(entity);
        this._context.SaveChanges();

         return this._adapter.ToDTO(entity);
     }
}

Now your base repository handles most of your base logic all you have to do is implement the adapter, e.g Automapper is a nice way to go.

public class OrderAdapter : IAdaptable<Order, OrderDTO>
{
    IOtherDependentService _service;
    public OrderAdapter(IOtherDependentService service)
    {
        this._service = service;
    }

    OrderDTO ToDTO(Order order)
    {
       var orderPars = this._service.GetParsFromOrder(order.Id);
       var dto = new OrderDTO{
           Order = order, 
           Pars1 = orderPars.Pars1, 
           Pars2 = orderPars.Pars2
       };

       return dto;

    }

    //.... Implement the rest of the interface you get the picture
}

You should be able to get the Gyst from this