Уровень приложений Java 8 и конкретное выходное преобразование - PullRequest
0 голосов
/ 29 апреля 2018

У меня есть мультипроект gradle с двумя подпроектами, пытающимися эмулировать гексагональную архитектуру:

  1. остальное-адаптер
  2. прикладной слой

Я не хочу, чтобы службы приложений отображали модели домена, и не хочу принудительно указывать конкретное представление в качестве выходных данных. Поэтому я хотел бы, чтобы что-то вроде сервисов приложений потребляло 2 аргумента (команду и something) и возвращало T. Клиент настраивает службу.

Адаптер остальных не имеет доступа к модели домена, поэтому я не могу вернуть модели домена и позволить адаптеру создать свое представление.

А как же something. Я попробовал:

  1. подпись <T> List<T> myUseCase(Command c, Function<MyDomainModel, T> fn). Прикладной уровень является владельцем функций преобразования (поскольку подпись использует MyDomainModel) и предоставляет словарь функций. Таким образом, контроллер остальных ссылается на одну из Fn. Оно работает. И я ищу лучшего пути. Более элегантный способ, если он существует.
  2. иметь подпись <T> List<T> myUseCase(Command c, FnEnum fn) Для каждого перечисления я связал функцию. С этим я обнаружил, что подпись более элегантна: потребитель обеспечивает, какую трансформацию он хочет получить из перечисления. Но не работает, потому что универсальный метод не компилируется. Не может быть решена. В настоящее время я не нашел пути.
  3. что-то с потребителем или поставщиком java 8 или что-то еще, но мне не удалось обернуться.

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

Ответы [ 2 ]

0 голосов
/ 29 апреля 2018

Я думаю, что вам нужно реализовать так называемый шаблон «Преобразователь данных».

Представьте, что у вас есть сценарий использования, который возвращает определенный объект домена (например, «Пользователь»), но вы не должны предоставлять домен клиентам. И вы хотите, чтобы каждый клиент выбирал формат возвращаемых данных.

Итак, вы определяете интерфейс преобразователя данных для объекта домена:

public interface UserDataTransformer {

    public void write ( User user );

    public String read();

}

Для каждого выходного формата вашим клиентам необходимо определить класс, реализующий интерфейс. Например, если вы хотите представить пользователя в формате XML:

public class UserXMLDataTransformer implements UserDataTransformer {

    private String xmlUser;

    @Override
    public void write(User user) {
        this.xmlUser = xmlEncode ( user );
    }

    private String xmlEncode(User user) {
        String xml = << transform user to xml format >>;
        return xml;
    }

    @Override
    public String read() {
        return this.xmlUser;
    }

}

Тогда вы делаете свою прикладную службу зависимой от интерфейса trasnsformer данных, вы вставляете его в конструктор:

public class UserApplicationService {

    private UserDataTransformer userDataTransformer;

    public UserApplicationService ( UserDataTransformer userDataTransformer ) {
        this.userDataTransformer = userDataTransformer;
    }

    public void myUseCase ( Command c ) {
        User user = << call the business logic of the domain and construct the user object you wanna return >> ;
        this.userDataTransformer.write(user);
    }

}

И, наконец, клиент может выглядеть примерно так:

public class XMLClient {

    public static void main ( String[] args ) {

        UserDataTransformer userDataTransformer = new UserXMLDataTransformer();
        UserApplicationService userService = new UserApplicationService(userDataTransformer);
        Command c = << data input needed by the use case >>;
        userService.myUseCase(c);
        String xmlUser = userDataTransformer.read();
        System.out.println(xmlUser);
    }

}

Я считаю, что выводом является строка, но вы можете использовать обобщенные значения, возможно, для возврата любого типа, который вы хотите.

Я не упомянул об этом, но этот подход при внедрении преобразователя в службу приложения следует шаблону «порт и адаптеры». Интерфейс преобразователя будет портом, а каждый реализующий его класс будет адаптером для нужного формата.

Кроме того, это был только пример. Вы можете использовать инфраструктуру внедрения зависимостей, такую ​​как Spring, чтобы создавать экземпляры компонентов и связывать их все. А также вы должны использовать корневой шаблон композиции, чтобы сделать это.

Надеюсь, этот пример помог.

0 голосов
/ 29 апреля 2018

Мне кажется, есть более элегантное решение для такого рода проблем: служба, которая принимает функцию, которая преобразует и создает выходные данные, которые предоставляет клиент.

Вы отправляете данные через границу между приложением и уровнем REST (и предположительно между приложением и потребителем REST); может быть полезно подумать о шаблонах сообщений .

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

interface ResponseBuilder {...}

void myUseCase(Command c, ResponseBuilder builder)

Адаптер REST обеспечивает реализацию ResponseBuilder, которая может принимать входные данные и генерировать из них некоторую полезную структуру данных.

Построитель ответов семантика (имена функций в интерфейсе) могут быть взяты из модели предметной области, но обычно аргументы будут либо примитивами, либо сообщениями других типов.

CQS подразумевает, что запрос должен возвращать значение; так что в этом случае вы можете предпочесть что-то вроде

interface ResponseBuilder<T> {
    ...
    T build();
}

<T> T myUseCase(Command c, ResponseBuilder<T> builder)

Если вы посмотрите внимательно, вы увидите, что здесь нет магии; мы просто перешли от прямой связи между приложением и адаптером к косвенной связи с контрактом.

EDIT

Мое первое решение заключается в использовании Function<MyDomainModel, T>, который немного отличается от вашего ResponseBuilder; но в том же духе.

Это почти двойственно. Возможно, вам было бы немного лучше с менее строгой подписью на myUseCase

<T> 
List<T> myUseCase(Command c, Function<? super MyDomainModel, T> fn)

Структура зависимостей по сути одна и та же - единственная реальная разница в том, с чем связан адаптер REST. Если вы считаете, что модель предметной области стабильна, а выходные представления сильно изменятся, то функциональный подход даст вам стабильный API.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...