{#========================================== Docs : "bootstrapping" ==========================================#}

Bootstrapping your app

The flexibility that Guice allows means that there are many ways of structuring your application, and that can be confusing at first. We'll show you some ways of doing it, but once you understand the basics, you could very well invent your own structure...

Note that, except if specified otherwise, the following examples all use the default Guice module, SpincastDefaultGuiceModule, so this Maven artifact must be added to your project :

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-default</artifactId>
    <version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>

{#========================================== Section "bootstrapping / everything_in_main" ==========================================#}

Everything in the main method

This is the quickest way to have an up and running Spincast application. It only uses the default components and is not very modular. But it is still a fully working application.

public class App {

    public static void main(String[] args) {

        Injector guice = Guice.createInjector(new SpincastDefaultGuiceModule(args));

        DefaultRouter router = guice.getInstance(DefaultRouter.class);
        router.GET("/").save(new DefaultHandler() {

            @Override
            public void handle(DefaultRequestContext context) {
                context.response().sendPlainText("In index!");
            }
        });

        Server server = guice.getInstance(Server.class);
        server.start();
    }
}

Explanation :

  • 5 : We create the Guice context using the provided SpincastDefaultGuiceModule module. This will bind a default implementation for all the required components.
  • 7 : We get the Router from the Guice context.
  • 8-14 : We add a GET route for the "/" index page. Here, we use an inline handler, but we will see that, with Java 8+, it is possible to use lambdas or method handlers too.
  • 16-17 : We get the Server instance from the Guice context and we start it!

{#========================================== Section "bootstrapping / main_class_as_boot" ==========================================#}

The main class used as the bootstrapping class

In this second example, instead of retrieving the Router and the Server from the Guice context, we let Guice inject them inside our App class. The App class is, in other words, now part of the Guice context!

public class App {

    public static void main(String[] args) {

        Injector guice = Guice.createInjector(new SpincastDefaultGuiceModule(args) {

            @Override
            protected void configure() {
                super.configure();
                bind(App.class).in(Scopes.SINGLETON);
            }
        });

        App app = guice.getInstance(App.class);
        app.start();
    }

    private final Server server;
    private final DefaultRouter router;

    @Inject
    public App(Server server, DefaultRouter router) {
        this.server = server;
        this.router = router;
    }

    public void start() {

        this.router.GET("/").save(new DefaultHandler() {

            @Override
            public void handle(DefaultRequestContext context) {
                context.response().sendPlainText("In index!");
            }
        });

        this.server.start();
    }
}

Explanation :

  • 1 : We will use the App main class not only as the class containing the main(...) method, but also as the bootstrapping class...
  • 5-12 : Inside the main(...) method, we create the Guice context using the provided SpincastDefaultGuiceModule module, but we also extend it, inline, to bind the App class too (line 10)!
  • 14-15 : We get the App instance from Guice and we call the start() method on it! Since the App instance is now managed by Guice, the required dependencies will be injected automatically and there is no need to manually retrieve the Server or the Router from Guice anymore.
  • 21-25 : The constructor that Guice will use (Note the @Inject annotation).
  • 27 : The start() method we call once the Guice context is created.
  • 29-34 : We add a GET Route for the "/" index page, here again using an inline Route Handler.
  • 37 : We start the Server.

{#========================================== Section "bootstrapping / custom_guice_modules" ==========================================#}

Using a custom Guice module

Here we are finally starting to talk "real life" structure! Instead of extending the default SpincastDefaultGuiceModule module inline, we will create a custom Guice module for our application. We're also going to create an AppConfig class to extend the default configurations and override some of them.

First let's create a custom configuration class for our application. The goal is to override some of the Spincast default configurations and to add some extra configurations, specific to our application.
// The interface
public interface AppConfig extends SpincastConfig {
    public String getAppName();
}

// The implementation
public class AppConfigDefault extends SpincastConfigDefault implements AppConfig {

    @Override
    public int getHttpServerPort() {
        return 8042;
    }

    @Override
    public String getAppName() {
        return "My supercalifragilisticexpialidocious app!";
    }
}

Explanation :

  • 2 : We define an interface for our custom configurations. We make this interface extend SpincastConfig because we're going to use it not only to add new configurations, but also to override some default Spincast configurations.
  • 3 : We add a new configuration, only required by our application : getAppName(). This configuration method will be used to retrieve the name of the application, at runtime.
  • 7 : The AppConfigDefault implementation class. We extend the default SpincastConfigDefault, to start with the default configurations, but of course we also implement our custom AppConfig interface.
  • 9-12 : Here, we override a default Spincast configuration : we change the port the HTTP server will start on to 8042!
  • 14-17 : We implement our custom, application specific, configuration.

Now that our custom configuration interface and implementation classes are ready, let's create a custom Guice module to tweak some bindings.

public class AppModule extends SpincastDefaultGuiceModule {

    public AppModule(String[] mainArgs) {
        super(mainArgs);
    }

    @Override
    protected void configure() {
        super.configure();

        bind(App.class).in(Scopes.SINGLETON);
        bind(AppConfigDefault.class).in(Scopes.SINGLETON);
        bind(AppConfig.class).to(AppConfigDefault.class).in(Scopes.SINGLETON);

        // Here you would also bind your other components : controllers, services, etc.
    }

    /**
     * We use our application config instead of the Spincast
     * default config.
     */
    @Override
    protected void bindConfigPlugin() {
        install(new SpincastConfigPluginGuiceModule(getRequestContextType(), 
                                                    getWebsocketContextType()) {

            @Override
            protected Class<? extends SpincastConfig> getSpincastConfigImplClass() {
                return AppConfigDefault.class;
            }
        });
    }
}

Explanation :

  • 1 : Our custom Guice module extends SpincastDefaultGuiceModule so we do not start from scratch: default bindings are kept. We're only going to change what's required!
  • 3-5 : We want to let the SpincastDefaultGuiceModule parent module bind the main arguments, so we add a constructor to receive them.
  • 8-9 : We override the configure() method to bind extra stuff. We keep the default bindings by calling super.configure().
  • 11 : We bind our App class (code listed below) which, again, will be the bootstrapping class for our application.
  • 12-13 : We bind our app specific configurations. Since our implementation class, AppConfigDefault, will be used by two different interfaces, AppConfig and SpincastConfig, we have to bind it itself as a singleton (more info).
  • 15 : This is a simple example, but in a real application you would also bind your controllers, your services, your repositories, etc.
  • 22-32 : Since we want Spincast to use our custom class, AppConfigDefault, as the implementation for the SpincastConfig interface, we need to override that binding! There are a couple of ways to do this, but here we're going to override the SpincastConfigPluginGuiceModule module installation, inline. You could also achieve this using an overriding module.

And, finally, here's our bootstrapping App class:

public class App {

    public static void main(String[] args) {

        Injector guice = Guice.createInjector(new AppModule(args));

        App app = guice.getInstance(App.class);
        app.start();
    }

    private final Server server;
    private final DefaultRouter router;
    private final AppConfig config;

    @Inject
    public App(Server server, DefaultRouter router, AppConfig config) {
        this.server = server;
        this.router = router;
        this.config = config;
    }

    protected Server getServer() {
        return this.server;
    }

    protected DefaultRouter getRouter() {
        return this.router;
    }

    protected AppConfig getConfig() {
        return this.config;
    }

    public void start() {

        getRouter().GET("/").save(context -> {
            context.response().sendPlainText("App name is : \"" +
                    getConfig().getAppName() + "\" and " + "server port is : " +
                    getConfig().getHttpServerPort());
        });

        getServer().start();
    }
}

Explanation :

  • 5 : In the main(...) method, we use our custom AppModule Guice module!
  • 7-8 : As in the previous example, we use the main App class as the bootstrapping class. We get its instance from Guice and call its start() method.
  • 15-20 : In the constructor, we ask Guice to inject a AppConfig instance, in addition to the Server and Router.
  • 22-32 : Let's use getters in this example, instead of accessing the member variables directly.
  • 34 : The start() method we call once the Guice context is created.
  • 36 : We add a GET route to the "/" index page. Here, for the handler, we use a Java 8's lambda.
  • 38 : In the Route Handler, we can now access our new configuration method getAppName()!
  • 39 : We can also access the getHttpServerPort() method that we overrided in our custom configuration implementation class.
  • 42 : We start the Server. The Server will start on port 8042.

{#========================================== Section "bootstrapping / overriding_modules" ==========================================#}

Using overriding modules

Instead of having a custom Guice module that extend SpincastDefaultGuiceModule and changes some bindings by overriding methods, you can also use overriding modules.

Let's first create a custom configuration class which changes the port the server will use :

public class AppConfigDefault extends SpincastConfigDefault {
    
    @Override
    public int getHttpServerPort() {
        return 8899;
    }
}

Explanation :

  • 1 : Our custom class extends SpincastConfigDefault since we want to keep the defaut configurations and only change the port the server will use.
  • 3-6 : We override the server port.

We then create a custom Guice module for our application:

public class AppModule extends AbstractModule {

    @Override
    protected void configure() {
        
        bind(App.class).in(Scopes.SINGLETON);
        bind(SpincastConfig.class).to(AppConfigDefault.class).in(Scopes.SINGLETON); 
        
        // Here you would also bind your other components : controllers, services, etc.
    }
}

Explanation :

  • 1 : Here, our module does not extend SpincastDefaultGuiceModule! It simply extends Guice's base AbstractModule.
  • 6 : We bind our App class (listed below).
  • 7 : We bind the SpincastConfig interface to our custom AppConfigDefault implementation. This is going to override the default binding.
  • 9 : Our example is very simple, but in a real life application you would also bind your controllers, your services, your repositories, etc.

Finally, we create our bootstrapping App class and tell Guice we want to use the default bindings provided by the SpincastDefaultGuiceModule module, but that we also want to add/override some bindings using our custom AppModule module!

public class App {

    public static void main(String[] args) {

        Injector guice = Guice.createInjector(Modules.override(new SpincastDefaultGuiceModule(args))
                                                     .with(new AppModule()));

        App app = guice.getInstance(App.class);
        app.start();
    }

    private final Server server;
    private final DefaultRouter router;

    @Inject
    public App(Server server, DefaultRouter router) {
        this.server = server;
        this.router = router;
    }

    public void start() {

        this.router.GET("/").save(new DefaultHandler() {

            @Override
            public void handle(DefaultRequestContext context) {
                context.response().sendPlainText("In index!");
            }
        });

        this.server.start();
    }
}

Explanation :

  • 5-6 : We create the Guice context using SpincastDefaultGuiceModule as the base module, but by using Modules.override(...).with(...) we specify that our custom module overrides already existing bindings).
  • 31 : Since we overrided the associated binding, the server will start on port 8899.

{#========================================== Section "bootstrapping / bootstrapping_as_config" ==========================================#}

Bootstrapping class as the configuration class

Now we'll see the kind of bootstrapping variations that are possible. Feel free to pick/invent one you are comfortable with.

In the following example, the App class not only acts as the bootstrapping class, but as the SpincastConfig implementation too! This will make it easy to override some default configurations.

public class App extends SpincastConfigDefault {

    public static void main(String[] args) {

        Injector guice = Guice.createInjector(new AppModule(args));

        App app = guice.getInstance(App.class);
        app.start();
    }

    private final Server server;
    private final DefaultRouter router;

    @Inject
    public App(Server server,
               DefaultRouter router) {
        super();
        this.server = server;
        this.router = router;
    }

    protected Server getServer() {
        return this.server;
    }

    protected DefaultRouter getRouter() {
        return this.router;
    }

    @Override
    public int getHttpServerPort() {
        return 8076;
    }

    public void start() {

        getRouter().GET("/").save(new DefaultHandler() {

            @Override
            public void handle(DefaultRequestContext context) {
                context.response().sendPlainText("In Index!");
            }
        });

        getServer().start();
    }
}

Explanation :

  • 1 : Our App class extends SpincastConfigDefault : it will be the implementation class for the configurations.
  • 14-20 : We ask Guice to inject the server and the router.
  • 30-33 : Since the App now acts as the configurations implementation class, we can directly override the server port!
  • 45 : The server will start on port 8076.

We ajust our custom AppModule Guice module:

public class AppModule extends SpincastDefaultGuiceModule {

    public AppModule(String[] mainArgs) {
        super(mainArgs);
    }

    @Override
    protected void configure() {
        super.configure();

        bind(App.class).in(Scopes.SINGLETON);

        // Here you would also bind your other components : controllers, services, etc.
    }

    @Override
    protected void bindConfigPlugin() {
        install(new SpincastConfigPluginGuiceModule(getRequestContextType(), 
                                                    getWebsocketContextType()) {

            @Override
            protected Class<? extends SpincastConfig> getSpincastConfigImplClass() {
                return App.class;
            }
        });
    }
}

Explanation :

  • 11 : We make sure our App bootstrapping class is bound.
  • 16-26 : We override the installation of the SpincastConfigPluginGuiceModule module, so our App class is used as the implementation for the SpincastConfig interface.

{#========================================== Section "bootstrapping / using controllers and services" ==========================================#}

Using controllers and services

In a real life application, your logic wouldn't all be inside a single App class! You would have controllers, services, repositories, etc. Here's a quick example of what this could look like.

Let's start with a "UserController" controller responsible for handling users related requests:

public interface UserController {

    // Route Handler to get a User as Json
    public void getUser(DefaultRequestContext context);
    
    // ...
}

Here's a possible implementation for this controller. Note that we inject an "UserService" service object, and we use it to get the user from our system (the actual implementation of this service is not important in this example) :

public class UserControllerDefault implements UserController {

    private final UserService userService;

    @Inject
    public UserController(UserService userService) {
        this.userService = userService;
    }

    protected UserService getUserService() {
        return this.userService;
    }

    @Override
    public void getUser(DefaultRequestContext context) {

        String userId = context.request().getPathParam("userId");

        User user = getUserService().getUser(userId);

        context.response().sendJson(user);
    }
}

Explanation :

  • 5-8 : In the constructor, we inject the required UserService service.
  • 15 : We implement our Route Handler.
  • 17 : We get the userId value from the request. As we'll see, this user id will be part of the path from the Route.
  • 19 : We use the injected service to get the user from our system. Note that this service could itself uses a repository object to access a data source where the users information is stored.
  • 21 : We send the user as Json. The user object will be serialized and sent using the "application/json" content-type.

As in the previous examples, let's once again define our routes directly in the App class (this is not required, by the way! You can inject the Router anywhere to add routes to it...). But this time, instead of defining our Route Handlers inline, we're going to do it inside our Controller :

public class App {

    public static void main(String[] args) {

        Injector guice = Guice.createInjector(new AppModule(args));

        App app = guice.getInstance(App.class);
        app.start();
    }

    private final Server server;
    private final DefaultRouter router;
    private final UserController userController;

    @Inject
    public App(Server server, DefaultRouter router, UserController userController) {
        this.server = server;
        this.router = router;
        this.userController = userController;
    }

    protected Server getServer() {
        return this.server;
    }

    protected DefaultRouter getRouter() {
        return this.router;
    }

    protected UserController getUserController() {
        return this.userController;
    }

    public void start() {

        getRouter().GET("/users/${userId}").save(getUserController()::getUser);

        getServer().start();
    }
}

Explanation :

  • 16 : We inject our Controller in the constructor.
  • 36 : We define a "/users/${userId}" Route which contains a dynamic parameter : "userId". Instead of defining our Route Handler inline, we use a Java 8's method handler to point to the getUser(...) Route Handler of our UserController controller.

Finally, we bind everything in our custom Guice module:

public class AppModule extends SpincastDefaultGuiceModule {

    public AppModule(String[] mainArgs) {
        super(mainArgs);
    }

    @Override
    protected void configure() {
        super.configure();

        bind(App.class).in(Scopes.SINGLETON);
        bind(UserController.class).to(UserControllerDefault.class).in(Scopes.SINGLETON);
        bind(UserService.class).to(UserServiceDefault.class).in(Scopes.SINGLETON);
    }
}

This example represents what we think is a good architecture to start a real life Spincast application. The only modification we would also recommend, is to use a custom Request Context type instead of the default one. Learn how to do this in The Request Context section!

{#========================================== Section "bootstrapping / boot_with_core" ==========================================#}

Using SpincastCoreModule directly

In all of the previous examples, we have been using the "spincast-default" Maven artifact, so a default implementation is automatically bound for all the required components.

By adding those default implementations, "spincast-default" also add transitive dependencies. For example, dependencies for some Jackson artifacts are added by the default Spincast Jackson Json plugin. Those dependencies may conflict with other dependencies you use in your application. This is a situation where you may want to start with the "spincast-core" artifact directly, instead of the default one. You may also want to start from scratch to have very fine control over what the resulting application will contain, to reduce the size of the application, etc.

To start a Spincast application from scratch, start with the "spincast-core" Maven artifact instead of "spincast-default":

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-core</artifactId>
    <version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>

Doing so, you start with the core code but you need to provide an implementation for all the required components, by yourself. You generaly provide those implementations by choosing and installing some plugins: you add their artifacts to your project and you install their Guice modules.

For example, to provide an implementation for the Server and for the TemplatingEngine components, you could use:

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-plugins-undertow</artifactId>
    <version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-plugins-pebble</artifactId>
    <version>{{spincast.spincastCurrrentVersion}}</version>
</dependency>

// ...

And then install their Guice modules:

public class AppModule extends SpincastCoreGuiceModule {

    public AppModule(String[] mainArgs) {
        super(mainArgs);
    }

    @Override
    protected void configure() {
        super.configure();

        install(new SpincastUndertowPluginGuiceModule(getRequestContextType(), 
                                                      getWebsocketContextType()));
                                                      
        install(new SpincastPebblePluginGuiceModule(getRequestContextType(), 
                                                    getWebsocketContextType()));
        
        // ...
    }
}

If you fail to provide an implementation for a required component, you will get this kind of error when trying to start the application:

> ERROR - No implementation for org.spincast.server.Server was bound.