Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DTO's and calls between services

Say I have two services in my service layer, ServiceA and ServiceB, each with an interface (IServiceA and IServiceB respectively).

The UI layer only has reference to the service interfaces which return DTOs from their methods. The concrete service classes are responsible for mapping the domain models (EF POCOs) into DTOs.

ServiceA takes a dependency on IServiceB via dependency injection using an IoC container, in order to call some methods on that service.

There are a couple of problems that arise in doing this:

  1. Unnecessary/duplicated mapping to and from a DTO just to call the method and/or consume the result.

  2. Tightly coupling the calling method to the DTO contracts of the called methods input parameters and return type.

Initially I thought to refactor the logic down into an internal method and call that from both services. However as ServiceA takes a dependency on the interface IServiceB the internal methods are not exposed.

How would you go about dealing with this issue?

Further information (added example code as requested):

// This is the domain model
public class Customer
{
    public int Id { get; set; }
    public string Name { get; set; }
}

// This is a dto for the domain model
public class CustomerDto
{
    public string Name { get; set; }
}

// Interface for ServiceA
public interface IServiceA
{
    void AddCustomer();
}

// ServiceA
public class ServiceA : IServiceA
{
    private readonly IServiceB _serviceB;

    // ServiceA takes in an IServiceB as a dependency
    public ServiceA(IServiceB serviceB)
    {
        _serviceB = serviceB;
    }

    public void AddCustomer()
    {
        var entity = new Customer();

        // !! This is the key part !!

        // I have to map to a dto in order to call the method on ServiceB.
        // This is a VERY simple example but this unnecessary mapping 
        // keeps cropping up throughout the service layer whenever
        // I want to make calls between services.

        var dto = Mapper.CreateFrom<CustomerDto>(entity);

        _serviceB.DoSomethingElseWithACustomer(dto);
    }
}

// Interface for ServiceB
public interface IServiceB
{
    void DoSomethingElseWithACustomer(CustomerDto customer);
}

// ServiceB
public class ServiceB : IServiceB
{
    public void DoSomethingElseWithACustomer(CustomerDto customer)
    {
        // Some logic here
    }
}
like image 679
Brett Postin Avatar asked Oct 03 '22 09:10

Brett Postin


2 Answers

Regarding the unncessary mapping to DTOs: Consider using Data Access Objects or Repositories if you prefer Domain Driven Design to access the database. Thus you can have a kind of "utility layer" beneath your service layer working directly with mapped (entity) objects.

Regarding the kind of coupling: ServiceB could implement more than one interface, especially one which is only visible on the server-side. ServiceA could depend on that interface to access more internal parts of ServiceB which are not suitable for publication to the client-side.

like image 107
oddparity Avatar answered Oct 06 '22 01:10

oddparity


We basically ended up with two choices to deal with our scenario.

  1. Split out our existing service layer into two separate layers:

    • A business logic layer that dealt only with the domain model and would allow inter-service calls without the need for dto mapping.

    • A "messaging/service" layer with the sole responsibility of massaging the data from the business logic layer ready for client consumption.

  2. As suggested by @oddparity, provide both a public and another internal interface for each service. The implemented public interface methods call into the internal methods.

We opted to go with option 2 as creating yet another layer of abstraction seemed like a lot of extra developer work, especially when only certain services needed inter-service calls.

As a result of this, we simply create internal interfaces for those services that need them.

This article gives a great overview of a layered architecture, and one that closely resembles our solution.

like image 28
Brett Postin Avatar answered Oct 05 '22 23:10

Brett Postin