DTO и звонки между сервисами

Скажем, у меня есть две службы на моем уровне обслуживания, ServiceA и ServiceB, каждая с интерфейсом (IServiceA и IServiceB соответственно).

Уровень пользовательского интерфейса имеет ссылку только на интерфейсы служб, которые возвращают DTO из своих методов. Конкретные классы обслуживания отвечают за отображение моделей предметной области (EF POCO) в DTO.

ServiceA принимает зависимость от IServiceB через внедрение зависимостей с использованием контейнера IoC для вызова некоторых методов этой службы.

При этом возникает пара проблем:

  1. Ненужное / дублированное отображение в DTO и из него просто для вызова метода и / или использования результата.

  2. Тесное связывание вызывающего метода с DTO-контрактами входных параметров и возвращаемого типа вызываемых методов.

Первоначально я думал преобразовать логику во внутренний метод и вызвать его из обеих служб. Однако, поскольку ServiceA зависит от интерфейса IServiceB, внутренние методы не отображаются.

Как бы вы справились с этой проблемой?

Дополнительная информация (добавлен пример кода по запросу):

// 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
    }
}

person Brett Postin    schedule 31.07.2013    source источник
comment
Не могли бы вы добавить код (без фактической обработки), чтобы было легче понять ваш вариант использования и текущую архитектуру?   -  person Mikhail Churbanov    schedule 31.07.2013
comment
Добавлен пример по запросу. Я попытался сократить его до минимума, чтобы выделить один из реальных проблемных случаев. Те же проблемы возникают с возвращаемыми значениями и т. Д.   -  person Brett Postin    schedule 31.07.2013
comment
Правильно ли я понимаю, что таким образом у вас есть два сгенерированных файла, в которых определяется CustomerDto (я не считаю ваше объявление проекта DTO) - в пространстве имен ServiceA и в ServiceB, и, несмотря на это, объявления равны - это разные сущности. И вы ищете способ обойти это?   -  person Mikhail Churbanov    schedule 31.07.2013
comment
@Mihail Я не уверен, что понимаю. Никакой код не генерируется. Класс модели и класс dto - это два отдельных класса с двумя разными целями. Модель предметной области моделирует мой бизнес-объект, в то время как dto предназначен для передачи данных по сети. Общедоступный API для служб принимает dtos в качестве входных данных и в результате возвращает dtos. Проблема в том, что когда я хочу вызвать службу из другой службы, и я работаю с моделью предметной области, существует много ненужного сопоставления только для вызова других вызовов службы, когда действительно сопоставление должно быть необходимо только при вводе ввода или подготовке возврата ценить.   -  person Brett Postin    schedule 31.07.2013


Ответы (3)


Что касается несобственного сопоставления с DTO: рассмотрите возможность использования объектов доступа к данным или Репозитории, если вы предпочитаете доменно-ориентированный дизайн для доступа к базе данных. Таким образом, у вас может быть своего рода «служебный уровень» под уровнем сервиса, работающий непосредственно с отображенными (сущностными) объектами.

Относительно вида связи: ServiceB может реализовывать более одного интерфейса, особенно тот, который виден только на стороне сервера. ServiceA может зависеть от этого интерфейса для доступа к дополнительным внутренним частям ServiceB, которые не подходят для публикации на стороне клиента.

person oddparity    schedule 31.07.2013
comment
Я обновил свой вопрос, включив в него пример кода. Я использую шаблон репозитория / единицы работы. Однако идея вызова других сервисов заключается в том, что может быть какая-то другая бизнес-логика, которую я хочу выполнить. Я бы не хотел помещать эту логику на уровень доступа к данным. Однако мне нравится ваше предложение о нескольких интерфейсах. - person Brett Postin; 31.07.2013

В итоге мы остановились на двух вариантах реализации нашего сценария.

  1. Разделите существующий уровень обслуживания на два отдельных слоя:

    • Уровень бизнес-логики, который имеет дело только с моделью предметной области и допускает межсервисные вызовы без необходимости отображения dto.

    • Слой «обмен сообщениями / сервисом» с исключительной ответственностью за массирование данных из уровня бизнес-логики, готовых для использования клиентом.

  2. Как предлагает @oddparity, предоставьте как общедоступный, так и другой внутренний интерфейс для каждой службы. Реализованные методы общедоступного интерфейса вызывают внутренние методы.

Мы выбрали вариант 2, поскольку создание еще одного уровня абстракции казалось большим объемом дополнительной работы разработчика, особенно когда только определенные службы нуждались в межсервисных вызовах.

В результате мы просто создаем внутренние интерфейсы для тех сервисов, которые в них нуждаются.

В этой статье говорится отличный обзор многоуровневой архитектуры, очень похожей на наше решение.

person Brett Postin    schedule 15.08.2013

Если я правильно понял, обе проблемы можно исправить, передав в ваши службы объекты домена вместо DTO. Таким образом вы можете избежать ненужных сопоставлений, а ваши сервисы останутся нетронутыми, если по какой-либо причине вам придется изменить интерфейс / контракт приложения.

IMHO, сопоставления DTO-доменов должны происходить только в границах вашего приложения. Например, сопоставление DTO с доменом должно быть в первую очередь вашим [действие контроллера | обработчик событий] do и отображение домена в DTO должно быть последним перед возвратом результата.

Надеюсь, это поможет.

person xdarsie    schedule 05.08.2013
comment
В моем приложении уровень обслуживания / BLL является границей приложения. Это API, который используется несколькими клиентами и, возможно, даже третьими сторонами. Как указано в этой статье, раскрытие модели предметной области за пределами уровня обслуживания создает тесную связь между вашими услугами и клиентами, чего я не хочу. DTO - это, по сути, контракт данных для каждого метода обслуживания, который может быть более гибким для изменения. - person Brett Postin; 06.08.2013
comment
Я предполагал, что у вас есть дополнительный уровень между службами приложения и клиентом приложения. Да, я согласен не разоблачать модель. Я не знаю ограничений вашего проекта, но, если возможно, я бы добавил этот дополнительный слой. Это хорошее место для выполнения сопоставления сущностей, проверки ввода, и это также позволит вам (по крайней мере, в этом конкретном случае) разорвать зависимости между вашими сервисами. - person xdarsie; 06.08.2013