Java8 应用层和具体的输出变换

Java 8 Application layer and specific output transformation

我有一个 gradle 多项目,其中有 2 个子项目试图模拟六边形架构:

  1. 休息适配器
  2. 应用层

我不希望应用程序服务公开域模型,也不希望强制将特定表示作为输出。所以我想要像应用程序服务这样的东西消耗 2 个参数(一个命令和 something)和 return 一个 T。客户端配置服务。

其余适配器无法访问域模型,因此我无法 return 域模型并让适配器创建其表示。

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 个消费者或供应商或其他东西,但我没能全神贯注。

我觉得对于这类问题有一个更优雅的解决方案:接受一个函数的服务,该函数可以转换和构建客户端提供的输出。

I'm feeling there's a more elegant solution for this kind of problem : a service which accepts a function that transforms and build an output that the client provides.

您正在跨应用程序和 REST 层之间的边界(并且可能是在应用程序和 REST 消费者之间)发送数据;考虑 消息传递 模式可能会有用。

例如,应用程序可以定义一个服务提供者接口,它定义一个contract/protocol用于从应用程序接受数据。

interface ResponseBuilder {...}

void myUseCase(Command c, ResponseBuilder builder)

REST 适配器提供了 ResponseBuilder 的实现,可以获取输入并从中生成一些有用的数据结构。

响应构建器语义(接口中函数的名称)可能来自域模型,但参数通常是原语或其他消息类型。

CQS 意味着查询应该 return 一个值;所以在那种情况下你可能更喜欢

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

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

如果你仔细看,你会发现这里没有魔法;我们只是简单地将应用程序和适配器之间的直接耦合切换为与合约的间接耦合。

编辑

My first solution is using a Function<MyDomainModel, T> which is a bit different from your ResponseBuilder ; but in the same vein.

它几乎是双重的。在 myUseCase

上使用限制较少的签名可能会好一点
<T> 
List<T> myUseCase(Command c, Function<? super MyDomainModel, T> fn)

依赖结构本质上是相同的——唯一真正的区别是 REST 适配器耦合到什么。如果你认为领域模型是稳定的,并且输出表示会发生很大变化,那么函数方法会给你稳定的 API.

不过,我怀疑您会发现输出表示在域模型稳定之前很久就稳定了,在这种情况下,ResponseBuilder 方法将是更稳定的选择。

我认为您需要实现的是所谓的 "Data Transformer" 模式。

假设您有一个用例 return 是某个域对象(例如 "User"),但您不应该向客户端公开域。并且您希望每个客户都选择 returned 数据的格式。

因此您为域对象定义了一个数据转换器接口:

public interface UserDataTransformer {

    public void write ( User user );

    public String read();

}

对于您的客户需要的每种输出格式,您定义一个 class 实现该接口。例如,如果您想以 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;
    }

}

然后你让你的应用服务依赖于data transnsformer接口,你在构造函数中注入它:

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);
    }

}

我认为输出是一个字符串,但您可以使用泛型来 return 任何您想要的类型。

我没有提到,但是这种将转换器注入应用程序服务的方法遵循 "port and adapters" 模式。转换器接口将是端口,每个 class 实现它都将是所需格式的适配器。

另外,这只是一个例子。您可以使用像 Spring 这样的依赖注入框架来创建组件实例并将它们连接起来。而且你应该使用组合根模式来做到这一点。

希望这个例子对您有所帮助。