All Downloads are FREE. Search and download functionalities are using the official Maven repository.

templates.docs.bootstrapping.html Maven / Gradle / Ivy

There is a newer version: 0.9.28
Show newest version
{#==========================================
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! We'll show you some examples, 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 SpincastDefaultGuiceModule, so this Maven artifact must be added to your project:

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-default</artifactId>
    <version>{{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));

        IDefaultRouter router = guice.getInstance(IDefaultRouter.class);
        router.GET("/").save(new IDefaultHandler() {

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

        IServer server = guice.getInstance(IServer.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 IServer server;
    private final IDefaultRouter router;

    @Inject
    public App(IServer server, IDefaultRouter router) {
        this.server = server;
        this.router = router;
    }

    public void start() {

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

            @Override
            public void handle(IDefaultRequestContext 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 contructor 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 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 IAppConfig extends ISpincastConfig {
    public String getAppName();
}

// The implementation
public class AppConfig extends SpincastConfig implements IAppConfig {

    @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 ISpincastConfig 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 AppConfig implementation class. We extend the default SpincastConfig, to start with the default configurations, but of course we also implement our custom IAppConfig 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(AppConfig.class).in(Scopes.SINGLETON);
        bind(IAppConfig.class).to(AppConfig.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 ISpincastConfig> getSpincastConfigImplClass() {
                return AppConfig.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, AppConfig, will be used by two different interfaces, IAppConfig and ISpincastConfig, we have to bind it 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, AppConfig, as the implementation for the ISpincastConfig 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 IServer server;
    private final IDefaultRouter router;
    private final IAppConfig config;

    @Inject
    public App(IServer server, IDefaultRouter router, IAppConfig config) {
        this.server = server;
        this.router = router;
        this.config = config;
    }

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

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

    protected IAppConfig 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 IAppConfig 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 AppConfig extends SpincastConfig {
    
    @Override
    public int getHttpServerPort() {
        return 8899;
    }
}

Explanation :

  • 1 : Our custom class extends SpincastConfig 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(ISpincastConfig.class).to(AppConfig.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 ISpincastConfig interface to our custom AppConfig 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 IServer server;
    private final IDefaultRouter router;

    @Inject
    public App(IServer server, IDefaultRouter router) {
        this.server = server;
        this.router = router;
    }

    public void start() {

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

            @Override
            public void handle(IDefaultRequestContext 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.

Here, we'll use the App class not only as the bootstrapping class, but as the ISpincastConfig implementation too! This will make it easier to override default configurations.

public class App extends SpincastConfig {

    public static void main(String[] args) {

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

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

    private final IServer server;
    private final IDefaultRouter router;

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

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

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

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

    public void start() {

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

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

        getServer().start();
    }
}

Explanation :

  • 1 : Our App class extends SpincastConfig : 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 ISpincastConfig> 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 ISpincastConfig 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 "IUserController" controller responsible for handling users related requests:

public interface IUserController {

    // Route handler to get a user as Json
    public void getUser(IDefaultRequestContext context);
    
    // ...
}

Here's a possible implementation for this controller. Note that we inject an "IUserService" service, and we use it to actually get the user from our system. The implementation of this service is not important in this example...

public class UserController implements IUserController {

    private final IUserService userService;

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

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

    @Override
    public void getUser(IDefaultRequestContext context) {

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

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

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

Explanation :

  • 5-8 : In the constructor, we inject the required IUserService 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 route path.
  • 19 : We use the injected service to get the user from our system. Note that this service would probably use a repository to access a data source where the users information is stored.
  • 21 : We use the response() add-on, and more specifically its sendJsonObj(...) method, to serialize the user to Json and return the result as the response.

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 the logic of our route handlers inline, we're going to use 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 IServer server;
    private final IDefaultRouter router;
    private final IUserController userController;

    @Inject
    public App(IServer server, IDefaultRouter router, IUserController userController) {
        this.server = server;
        this.router = router;
        this.userController = userController;
    }

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

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

    protected IUserController 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 the logic of our handler inline, we use a Java 8's method handler pointing to the getUser(...) route handler of our IUserController 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(IUserController.class).to(UserController.class).in(Scopes.SINGLETON);
        bind(IUserService.class).to(UserService.class).in(Scopes.SINGLETON);
    }
}

This last 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 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 want to use in your application. This is a situation where you may want to start with the core directly, without any default implementations.

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>{{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 do so 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 IServer and for the ITemplatingEngine components, you could use:

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

<dependency>
    <groupId>org.spincast</groupId>
    <artifactId>spincast-plugins-pebble</artifactId>
    <version>{{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:

1) No implementation for org.spincast.server.IServer was bound.





© 2015 - 2025 Weber Informatics LLC | Privacy Policy