DDD/HexArch提示与技巧:使用ComponentScan将领域绑定到Spring上下文 - beyondxscratch


六边形架构告诉我们,领域内不应该存在任何框架,以避免技术意外的复杂性,并且无需重新开发业务逻辑部分即可轻松迁移到新的结构框架(或主要版本)。这意味着当您使用Spring时,您不能依赖任何构造型注释,例如域内的@Service@Component
因此,我们通常在Spring Configurations中使用bean工厂方法,并使用大量样板代码来实例化域服务,存储库和存根,因为我们认为我们不能将ComponentScan  用于领域对象。

@Configuration
public class DomainConfiguration {

    @Bean
    public RetrieveSanitaryGrades retrieveSanitaryGrades() {
        return new RetrieveSanitaryGrades();
    }
    
    @Bean
    public SearchRestaurants restaurantsFinder(Restaurants restaurants, 
                                               OpeningHours openingHours, 
                                               RetrieveSanitaryGrades retrieveSanitaryGrades) {
        return new RestaurantsFinder(restaurants, openingHours, retrieveSanitaryGrades);
    }
    
}

本文将向您展示如何在不违反六边形架构规则的情况下利用组件扫描与Koltin中的代码示例。如果您正在使用Java,请不要担心,代码将大致相同,您将不得不调整一点语法。

Spring组件扫描查找带有Spring构造型的带注释类,以标识要在ApplicationContext中注册的对象。因此,这些对象将不可避免地依赖于Spring Framework,这基本上是领域实现的一个问题。。为了摆脱这种限制,我们将使用六边形体系结构使用的技巧来确保您的域永远不会依赖于持久层,这就是:依赖性反转。
诀窍很简单,在域内创建描述性注释,以识别要向ApplicationContext公开的对象 - 通常是DomainServices和Stubs。

import java.lang.annotation.Inherited

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@Inherited
annotation class DomainService

@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@Inherited
annotation class Stub

保留策略设置为RUNTIME  以允许Spring发现它们。使用其他策略时,注释将在运行时被丢弃,这对我们的情况没有帮助。您现在可以使用自定义注释安全地注释要公开的领域对象。

您还可以为其他DDD对象(如Aggregate,Entity,ValueType,Repository)创建描述性注释,即使您没有将它们公开给ApplicationContext也是如此。这是一个很棒的技术,可以让人们进入领域驱动设计(使用一些javadoc,请参阅下面的示例),或者只是为了更好地了解您的业务及其建模方式。
这有时对于检测代码评审中的一些错误很有用。就像包含实体的ValueType,带有副作用的ValueType一样......有时很难确定我们正在处理的对象的性质,尤其是在复杂丰富的业务领域。

/**
 * <p>
 * A DDD aggregate is a cluster of domain objects that is treated as a
 * single unit to ensure consistency across them.
 * </p>
 *
 * @see <a href= "https://martinfowler.com/bliki/DDD_Aggregate.html">
 *  Aggregates in Domain Driven Design
 * </a>
 */

public @interface Aggregate {

}

现在,我们将告诉Spring像对待那些对象@Service,@Component注解一样,通过配置我们的自定义域注释在基础设施扫描。只需创建一个DomainConfiguration并注释如下:

@Configuration
@ComponentScan(
        // basePackageClasses = [Recommendation::class],
        includeFilters = [ComponentScan.Filter(type = FilterType.ANNOTATION, value = [DomainService::class, Stub::class])])
class DomainConfiguration

请注意,ComponentScan默认情况下仅在SpringBootApplication包中查找,包括其子包。因此,如果您的域不在此层次结构中,则需要指定一个基本包类,以识别域的根包(请参阅上面的注释代码)。通常,我们在这里使用主域聚合,因为常见的DDD约定鼓励我们在他之后命名根域包。

您还可以使用basePackages并将包名称作为String。但是这种做法是不鼓励的,因为不能适应包重构。

这是这种方法的限制:

  • 如果需要一些基于条件或基于配置文件的bean构造,则必须切换回bean工厂。
  • 主观:有些人不喜欢注释。
  • 如果您使用的是Kotlin,对maven插件开放的类现在可以扩展,因此它违反了开放式原则。

如果您想进一步使用Maven,Koltin和SpringBoot实现基于六边形体系结构的应用程序,您可以查看此存储库