templates.docs.bootstrapping.html Maven / Gradle / Ivy
Show all versions of spincast-website Show documentation
{#==========================================
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.