1

I'm building a spring boot application which should support multiple clients. Currently, I've common logic defined at the parent project and customizations at child project. In some cases, the clients may ask additional features or may need extra customization of existing feature which is defined in this child using @Primary annotation. The idea here is to keep core logic and customizations separate.

Currently, We are creating different branches for different clients in the child project and customizations are carried out in those which is not the best solution. As no of clients increase no of branches will also increase.

I want a single code where I'll be defining all the customization for all the clients and feature available to each client should be controllable.

I know that this can be achieved by using profiles. But still, even in that case, I've to define profiles for all the component annotations which are cumbersome.

So recently I came up with an idea of using @Componentscan with a property to control the client logic with the following directory structure.

Parent:

.
└── src
    └── main
        └── java
            ├── com.test.parent.controller
            ├── com.test.parent.dao
            └── com.test.parent.service

Child:

.
└── src
    └── main
        └── java
            ├── com.test.child.client1.controller
            ├── com.test.child.client1.dao
            ├── com.test.child.client1.service
            ├── com.test.child.client2.controller
            ├── com.test.child.client2.dao
            ├── com.test.child.client2.service
            ├── com.test.child.client3.controller
            ├── com.test.child.client3.dao
            └── com.test.child.client3.service

Mainclass.java

package com.test.child;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = { "com.test.parent.*","com.test.child.${clientname:default}.*" })
public class MainClass {

    public static void main(String[] args) {
        SpringApplication.run(MainClass.class, args);
    }

}

But even, In this case, I'm controlling clients customization by using property clientname and there is a risk of exposing other client's customization by, passing other client's name.

  • Is it possible to control packages or business logic to be included or excluded during mvn package or mvn install itself provided that the code remains single? i.e isolate logic during build time itself instead of run time.
  • Seems maven-compiler-plugin plugin provides such feature by specifying includes along with pattern. But how to do the same in spring-boot-maven-plugin such that the build is controlled by passing property during mvn package or mvn install?

So basically I'm looking something like a filter which supports pattern to include/exclude package/code during mvn build lifecycle.

Not sure <resources><resource>..<\resources><\resource> can be used to filter code apart from resources.

Community
  • 1
  • 1
Mani
  • 5,401
  • 1
  • 30
  • 51
  • Are you trying to do simultaneous multitenant in a single deployment or customizable white-label independent deployments? (I suspect that what you *really* want is a jar per client with an auto-configuration class that switches that client's pieces on either by virtue of being present at runtime or through some profile or other configuration switch.) – chrylis -cautiouslyoptimistic- Jun 05 '19 at 08:00
  • Kind of independently deployable jar for each client from a single code. But the jar that is built for a client should only have the respective classes and should not have the other ones. – Mani Jun 05 '19 at 08:04

2 Answers2

1

The Spring Boot auto-configuration mechanism has the ability to automatically detect auto-configuration classes that are on the classpath at runtime and import them into the context (sometimes using conditions to decide whether to do so).

In your case, it sounds like a solid approach is to write and package your general Spring Boot application as a single shared jar. Then each client's specific code will live in a "plugin" jar that declares Client1AutoConfiguration; when launching the application, tell Boot to also load the plugin jar.

If you have defaults in your main jar that need to be overridden in plugins, use @ConditionalOnMissingBean (see DataSourceAutoConfiguration for a good example) and annotate your plugin auto-configuration classes with @AutoConfigureBefore(MainAutoConfiguration.class).

chrylis -cautiouslyoptimistic-
  • 75,269
  • 21
  • 115
  • 152
  • but if I create each client's customization as a separate plugin then if I have 100 clients then I'll end up with 100 client jars. Even if we try to keep the jar size minimalistic. While booting up spring has to search and include one plugin from 100 plugins. Won't it impact jar size and startup time? I'm trying to keep jar size minimal and each client's code isolated at build time itself. – Mani Jun 05 '19 at 08:16
  • @Mani First, don't underestimate the huge amount of scanning Spring is already doing. Second, my recommendation is to ship `mainapp.jar` and then `client1.jar`, `client2.jar`, and so on. Each client only uses their own particular plugin. – chrylis -cautiouslyoptimistic- Jun 05 '19 at 08:18
  • Over a period of time won't maintaining those 100 plugins over version control and bug database be difficult? – Mani Jun 05 '19 at 08:22
  • @Mani You're going to have to maintain the code somehow, and if you don't want to ship client 2's code to client 1, you *have* to separate them. Keep them in Maven modules alongside your main build if you like. – chrylis -cautiouslyoptimistic- Jun 05 '19 at 08:24
  • ok. but still, I'm looking for isolation at build time instead of runtime. Runtime isolation can anyway be done by the existing approach itself right? I agree managing it through the plugin is also another way of runtime control. – Mani Jun 05 '19 at 08:28
  • Building the jars as Maven modules does provide classpath separation at build-time, just not source separation (which gets you back to multiple repositories and all). – chrylis -cautiouslyoptimistic- Jun 05 '19 at 08:31
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/194476/discussion-between-mani-and-chrylis). – Mani Jun 05 '19 at 08:33
0

Finally figured out. I have to use maven-compiler-plugin along with spring-boot-maven-plugin in order to exclude files or folders. Since spring-boot-maven-plugin can only be used to exclude devtools or artifacts or groupids, maven-compiler-plugin is used to exclude/include files/folders.

<project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>${spring.boot.version}</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
   ........
   ........
   ........
   <properties>
      <java.version>1.8</java.version>
      <client.name>defaultclientname</client.name>
   </properties>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <configuration>
               <includes>
                  <include>com/test/child/MainClass.java</include>
                  <!--Spring boot mainclass entry-->
                  <include>com/test/child/${client.name}/**</include>
                  <!--Includes only the specified client.-->
               </includes>
            </configuration>
         </plugin>
      </plugins>
   </build>
</project>

Since client.name is defined in property it becomes the default value and the same can be overridden during build by passing respective value like mvn clean package -Dclient.name=client1.

Now there is no necessity of runtime control and the client customizations are completly isolated.

Mani
  • 5,401
  • 1
  • 30
  • 51
  • can you please share some sample example project. can we override beans which are created in parent project with client project ?? – Mahesh Jamdade Nov 22 '19 at 06:23
  • @maheshmnj yes if you you can override parent beans in the child by using `@Primary` annotation or you can also use `@Qualifier` annotation to declare additional bean. – Mani Nov 22 '19 at 06:39
  • Okay thanks if you could share some example or drop some links it would be a great help I just want to see the configuration for the multi module project and the directory structure – Mahesh Jamdade Nov 22 '19 at 07:13
  • there are different ways to create multi-module project [see this spring example](https://spring.io/guides/gs/multi-module/). For bean overriding you can refer [this](https://brudenko.com/spring-bean-override) or google for bean overriding. – Mani Nov 22 '19 at 07:20