0

I am looking for the best way to implement a single-module Spring Boot project, which can run as multiple processes (1) serving REST API or (2) running one of many CLI processes.

Details:

We have three components in a simplified version of our application:

  • Ingestion Process, which runs as a command line process. It connects to an external data source, fetches the data, and produces it to a Pulsar queue.
  • Analysis Process, which also runs as a command line process. It consumes any data on the Pulsar queue, does some processing on it, and saves results to a MySQL database.
  • Web REST App, which runs a server listening on port 8080. Provided REST APIs query the MySQL data and return reports based on already-analyzed data.

Question:

  • If I create a Spring Boot Application, how do I have multiple entry points so when running it I can say which of the three processes I want to start.
  • Use of ApplicationRunner will not work as it will start the web app + ingestion process + analysis process as a single process.
  • Option is to write some logic in the main spring boot application as “dispatcher” so it starts the correct process based on some command line params. For example: java MyApp -run web-app or java MyApp -run ingestion-process
  • I know, using multiple modules is an option but I think that adds complexity that our small team wants to avoid if we can as we build MVC of an application. Especially as I expect in the final version of the app to have 20-30 different processes to fetch data from different external data sources. I want to run each one as separate command line processes, even if each one is lightweight.
  • I see Spring Modulith as a way to organize Spring projects in lightweight modules, but I don’t see anything about different applications I can start.

Any advice on how to architect will be useful. I am new to Spring Boot but have otherwise been building complex Java-based apps for 10+ years; and now trying to learn best practices within the Spring world.

I am looking for best practices on how to architect the app. We are a small team with deadline to deliver an MVP fast.

Nawab
  • 1

1 Answers1

0

You can have your code have three different "identites" by adding gating logic to your code based on some input factor that your code uses to decide which identity to take on. You can conditionally start a web server based on that input factor. Here's a simple example that starts the embedded Tomcat web server only if no command line arguments are provided, and will run an operation based on the first command line argument if it is supplied:

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    public static void main(String[] args) {
        WebApplicationType appType;
        if (args.length > 0)
            appType = WebApplicationType.NONE;
        else
            appType = WebApplicationType.SERVLET;
        SpringApplication application = new SpringApplication(DemoApplication.class);
        application.setWebApplicationType(appType);
        application.run(args);
    }

    public void run(String... args) throws Exception {
        if (args.length > 0) {
            if (args[0].equals("operation1")) {
                System.out.println("Did operation 1");
            }
            else if (args[0].equals("operation2")) {
                System.out.println("Did operation 2");
            }
        }
    }
}

This application will run continuously if given no command line args, serving any endpoints you've defined, and will do the appropriate app if given a first command line arg of "operation1" or "operation2", and will do nothing (will just exit) if given any other first argument.

You can, of course, put the code for each of the three identities in different modules, but that is not necessary. The defined operations can either perform some action and then exit, run continuously in a loop, or launch a thread to handle whatever that operation does. If you launch a thread and don't make it a daemon thread then the main thread (the runner) can complete and exit, and the app will remain alive for as long as the op thread continues to run.

CryptoFool
  • 21,719
  • 5
  • 26
  • 44
  • Thanks. Makes sense. I was hoping there is another way baked in Spring (or its Moduliths project) rather than a long if then else dispatcher. But this works, thanks. – Nawab Nov 07 '22 at 22:26
  • There are many other ways you could go. For example, you could have a map that contains the various trigger values (the 'operation1', 'operation2', etc. in my example) as the keys, and then a lambda function as each of the values. So you look up the trigger value in the map to get the lambda function, and then you call that function. I can't think what Spring could do for you that you can't do yourself. If you can better describe the set of processes that you want to run, and how you'd like to select which to run, maybe I or someone else can give you other options. – CryptoFool Nov 08 '22 at 00:44
  • ...if you wanted to leverage Spring, you could define a Java interface to describe each of your processes generically. Then you could define a Spring Bean for each process, each of them implementing that interface. You could have Spring give you one of those Beans based on a qualifier that you've assigned to each bean. You could do the same without Spring too...just have a map that matches names with class objects, like `{ "foo": Foo.class, ...}`, and then you look up the name to get the class object, and then you instantiate the class to get an instance of it that implements a process. – CryptoFool Nov 08 '22 at 00:51