templates.docs.bootstrapping.html Maven / Gradle / Ivy
{#==========================================
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()) {
            @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-31 : 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()) {
            @Override
            protected Class<? extends ISpincastConfig> getSpincastConfigImplClass() {
                return App.class;
            }
        });
    }
} 
                
                
                    Explanation :
                    
                        - 
                            11 : We make sure our 
App 
                            bootstrapping class is bound. 
                         
                        - 
                            16-25 : 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(IAppRequestContext 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:
        
        
            
                
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().sendJson(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 sendJson(...) 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()));
        install(new SpincastPebblePluginGuiceModule(getRequestContextType()));
        // ...
    }
}
                 
            
        
        
            
                
                     
                    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.