简体   繁体   English

SpringApplicationBuilder 结合小型 SpringBoot 应用程序(模块化单体)

[英]SpringApplicationBuilder to combine small SpringBoot applications (modular Monolith)

I am trying to build a "Main" SpringBoot application which shall be composed out of many small independent SpringBoot Applications.我正在尝试构建一个“主要”SpringBoot 应用程序,该应用程序应由许多小型独立 SpringBoot 应用程序组成。 The main application talks with each of the small SpringBoot applications to query data or to modify data.主应用程序与每个小型 SpringBoot 应用程序对话以查询数据或修改数据。 Each of the small application fulfills a certain domain.每个小应用程序都满足某个领域。 For example a dependency / SpringBoot Application for user management, mail transfer, product management, order fulfilling.例如,用于用户管理、邮件传输、产品管理、订单履行的依赖项/SpringBoot 应用程序。 Each of the small application uses an own database which is separated from the others.每个小型应用程序都使用自己的数据库,该数据库与其他应用程序分开。

I've added the small applications as dependency to my main application.我已将小型应用程序作为依赖项添加到我的主应用程序中。 What I'm now facing is the problem or the fact that the main instantiates all beans from the dependency applications.我现在面临的问题或事实是 main 实例化了依赖应用程序中的所有 bean。 I know, this is how SpringBoot works.我知道,这就是 SpringBoot 的工作原理。 The problem is that there are some components or classes in each dependency with the same name.问题是每个依赖项中都有一些具有相同名称的组件或类。 So running the application just crashes because there are beans with the same name which is correct.所以运行应用程序只是崩溃,因为有相同名称的 bean 是正确的。 Further the main tries to instantiate a Hikari DB connection but should not have any db connection.此外,主要尝试实例化 Hikari 数据库连接,但不应有任何数据库连接。 I think that is because the other applications have a db connection.我认为这是因为其他应用程序具有数据库连接。 Therefore, the main also tries to build up a db connection.因此,main 也尝试建立一个 db 连接。

The idea behind this is to build a modular monolith so in future one or more of the dependencies can be replaced by a microservice.这背后的想法是构建一个模块化的单体,以便将来可以将一个或多个依赖项替换为微服务。 Currently, building a microservice architecture is not required and not claimed.目前,构建微服务架构不是必需的,也没有声明。 Therefore, I would like to build a modular monolith architecture.因此,我想构建一个模块化的单体架构。

Is there a way that each of the Spring Boot application just uses their own Beans and the main application can just invoke a certain interface / API of the others?有没有办法让每个 Spring 引导应用程序只使用自己的 Bean,而主应用程序只能调用某个接口/其他 API? Just like the other services expose a REST API but without exposing a REST API.就像其他服务公开 REST API 但不公开 REST ZDB974238714CA8DE634A7ACE1

Would be the SpringBootApplicationBuilder ( https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/builder/SpringApplicationBuilder.html ) using child / siblings a way to achieve my goal? Would be the SpringBootApplicationBuilder ( https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/builder/SpringApplicationBuilder.html ) using child / siblings a way to achieve my goal? Or do I miss something and there is no way to achieve my goal?还是我错过了什么,没有办法实现我的目标?

In case of Spring Boot's children - there will be potential conflicts in resources and bindings: you'll not be able to bind 2 servlet container instances to same port anyway, so be required to separate them.如果是 Spring Boot 的子级 - 资源和绑定可能会发生冲突:无论如何您都无法将 2 个 servlet 容器实例绑定到同一个端口,因此需要将它们分开。

Think will be better to use single Spring Boot application and use dedicated classes with @Configuration annotation per child 'application' as starting point:认为最好使用单个 Spring 引导应用程序并使用带有 @Configuration 注释的专用类作为起点:

@Configuration
public class SomeModuleConfig {

@Bean
public Bean myModuleBean() {}

}

To avoid global scan,you can play with @ComponentScan include/exclude filters: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.html为避免全局扫描,您可以使用@ComponentScan 包含/排除过滤器: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/ComponentScan.Z92538EZ3AD85

Maybe you can try to separate modules using context hierarchy, but of course, as @Alex said you must take care of proper component scanning:也许您可以尝试使用上下文层次结构分离模块,但当然,正如@Alex 所说,您必须注意正确的组件扫描:

public class Bootstrap {

    private static final Logger LOGGER = LoggerFactory.getLogger(Bootstrap.class);

    private static final String CONFIG_LOCATION = "spring.config.location=classpath:/config/";

    public static void main(String[] args) {

        SpringApplicationBuilder parentBuilder = new SpringApplicationBuilder(CtxParent.class).web(WebApplicationType.NONE);

        ConfigurableApplicationContext context =
            parentBuilder.parent(CtxParent.class)
                .web(WebApplicationType.SERVLET)
                .properties("spring.config.name=application",CONFIG_LOCATION).run(args);
            parentBuilder.child(CtxModuleOne.class)
                .web(WebApplicationType.NONE) // whatever
                .properties("spring.config.name=module-one",CONFIG_LOCATION).run(args);
            parentBuilder.sibling(CtxModuleTwo.class)
                .web(WebApplicationType.NONE) // whatever
                .properties("spring.config.name=module-two",CONFIG_LOCATION).run(args);

    }
}

Please ignore my first paragraph (For the problem of bean instanciation, if you have @ComponentScan in your root application, you could try to remove it and add @SpringBootApplication(scanBasePackages = "com....") to each individual module's exposed service or controller. The main application should not see everything inside the child modules. The components (beans) stay only in the contexts of each module whilst the main application interacts with each module's API.)请忽略我的第一段(对于 bean 实例化的问题,如果您的根应用程序中有 @ComponentScan,您可以尝试将其删除并将 @SpringBootApplication(scanBasePackages = "com....") 添加到每个单独模块的公开服务或 controller。主应用程序不应看到子模块内的所有内容。组件(bean)仅保留在每个模块的上下文中,而主应用程序与每个模块的 API 交互。)

For the database issue, implement separate configurations for each module.对于数据库问题,为每个模块实现单独的配置。 Bellow is an example of a module's database configuration in my application: Bellow 是我的应用程序中模块数据库配置的示例:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        basePackages = "com.path.to.academy.persistency",
        entityManagerFactoryRef = "academyEntityManager",
        transactionManagerRef = "academyTransactionManager"
)
public class PersistenceAcademyAutoConfig {

    @Autowired
    ExternalConfigurations externalConfigurations;

    @Autowired
    AcademyExternalConfigurations academyExternalConfigurations;

    public PersistenceAcademyAutoConfig(
    ) {
        super();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean academyEntityManager() {
        final LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(academyDataSource());
        em.setPackagesToScan("com.path.to.academy.domain");

        final HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        final HashMap<String, Object> properties = new HashMap<String, Object>();
        properties.put("hibernate.hbm2ddl.auto", externalConfigurations.getHibernateDdl());
        properties.put("javax.persistence.create-database-schemas", externalConfigurations.getDbSchemaCreationDirective());
        properties.put("hibernate.dialect", externalConfigurations.getHibernateDialect());
        properties.put("hibernate.show_sql", externalConfigurations.getShowSql());

        em.setJpaPropertyMap(properties);

        return em;
    }

    @Bean
    public DataSource academyDataSource() {
        DataSourceBuilder dataSourceBuilder = DataSourceBuilder.create();
        dataSourceBuilder.url(academyExternalConfigurations.getDbUrl());
        dataSourceBuilder.username(externalConfigurations.getDbUser());
        dataSourceBuilder.password(externalConfigurations.getDbPassword());
        dataSourceBuilder.driverClassName(externalConfigurations.getDbDriverClassName());
        return dataSourceBuilder.build();
    }

    @Bean
    public PlatformTransactionManager academyTransactionManager() {
        final JpaTransactionManager transactionManager = new JpaTransactionManager();
        transactionManager.setEntityManagerFactory(academyEntityManager().getObject());
        return transactionManager;
    }
}

Hope it helps!希望能帮助到你!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM