-
Notifications
You must be signed in to change notification settings - Fork 19
15. Modules
Most nontrivial Utterlyidle applications are built of many modules. We recommend creating different modules for different vertical slices of your application (divided by business capabilities) as opposed to horizontal slices (divided by technical layers).
GOOD:
import com.googlecode.utterlyidle.Application;
import com.googlecode.utterlyidle.BasePath;
import com.googlecode.utterlyidle.RestApplication;
import customer.CustomerModule;
import order.OrderModule;
public class ShoppingApplication extends RestApplication {
public ShoppingApplication(BasePath basePath) {
super(basePath);
add(new CustomerModule());
add(new OrderModule());
}
public static void main(String[] args) {
Application application = new ShoppingApplication(BasePath.basePath("/foo"));
}
} }
}
BAD:
import com.googlecode.utterlyidle.Application;
import com.googlecode.utterlyidle.BasePath;
import com.googlecode.utterlyidle.RestApplication;
public class ShoppingApplication extends RestApplication {
public ShoppingApplication(BasePath basePath) {
super(basePath);
add(new UIModule());
add(new DomainModule());
add(new PersistenceModule());
}
public static void main(String[] args) {
Application application = new ShoppingApplication(BasePath.basePath("/foo"));
}
} }
}
To define RESTful resources we use ResourceModule that provides addResources method.
package customer;
import com.googlecode.utterlyidle.Resources;
import com.googlecode.utterlyidle.modules.Module;
import com.googlecode.utterlyidle.modules.ResourcesModule;
import static com.googlecode.utterlyidle.annotations.AnnotatedBindings.annotatedClass;
public class CustomerModule implements ResourcesModule {
public Resources addResources(Resources resources) throws Exception {
return resources.add(annotatedClass(CustomerResource.class));
}
}
If a resource depends on an object that needs to be created on each request we need to use a RequestScopedModule to provide this per request collaborator.
package customer;
public class CustomerResource {
private CustomerService service;
public CustomerResource(CustomerService service) {
this.service = service;
}
// resource methods
}
package customer;
import com.googlecode.utterlyidle.Resources;
import com.googlecode.utterlyidle.modules.Module;
import com.googlecode.utterlyidle.modules.RequestScopedModule;
import com.googlecode.utterlyidle.modules.ResourcesModule;
import com.googlecode.yadic.Container;
import static com.googlecode.utterlyidle.annotations.AnnotatedBindings.annotatedClass;
public class CustomerModule implements ResourcesModule, RequestScopedModule {
public Resources addResources(Resources resources) throws Exception {
return resources.add(annotatedClass(CustomerResource.class));
}
public Container addPerRequestObjects(Container container) throws Exception {
return container.add(CustomerService.class);
}
}
Some objects should be created only once per application. Some examples would be a database storage, scheduler, properties, date provider etc. ApplicationScopedModule defines addPerApplicationObjects method where you can inject those object that will be created only once in the application lifecycle.
package customer;
import com.googlecode.utterlyidle.Resources;
import com.googlecode.utterlyidle.modules.ApplicationScopedModule;
import com.googlecode.utterlyidle.modules.Module;
import com.googlecode.utterlyidle.modules.RequestScopedModule;
import com.googlecode.utterlyidle.modules.ResourcesModule;
import com.googlecode.yadic.Container;
import static com.googlecode.utterlyidle.annotations.AnnotatedBindings.annotatedClass;
public class CustomerModule implements ResourcesModule, RequestScopedModule, ApplicationScopedModule {
public Resources addResources(Resources resources) throws Exception {
return resources.add(annotatedClass(CustomerResource.class));
}
public Container addPerRequestObjects(Container container) throws Exception {
return container.add(CustomerService.class);
}
public Container addPerApplicationObjects(Container container) throws Exception {
return container.add(CustomerStorage.class, DBCustomerStorage.class);
}
}You can use this module if you want to create objects that will be used in the resource method arguments.
package customer;
import com.googlecode.utterlyidle.Response;
import com.googlecode.utterlyidle.annotations.POST;
public class CustomerResource {
private CustomerService service;
public CustomerResource(CustomerService service) {
this.service = service;
}
@POST
public Response create(Customer customer) {
Customer createdCustomer = service.create(customer);
return null;
}
// resource methods
}In the example above we want to pass a customer object instead of several different String values matching form parameters.
package customer;
import com.googlecode.utterlyidle.Resources;
import com.googlecode.utterlyidle.modules.*;
import com.googlecode.yadic.Container;
import static com.googlecode.utterlyidle.annotations.AnnotatedBindings.annotatedClass;
public class CustomerModule implements ResourcesModule, RequestScopedModule, ApplicationScopedModule, ArgumentScopedModule {
public Resources addResources(Resources resources) throws Exception {
return resources.add(annotatedClass(CustomerResource.class));
}
public Container addPerArgumentObjects(Container container) throws Exception {
return container.addActivator(Customer.class, CustomerActivator.class);
}
// other methods
}ArgumentScopedModule provides injection point where we can define how the Customer object should be created.
package customer;
import com.googlecode.utterlyidle.FormParameters;
import java.util.concurrent.Callable;
public class CustomerActivator implements Callable<Customer> {
private FormParameters formParameters;
public CustomerActivator(FormParameters formParameters) {
this.formParameters = formParameters;
}
public Customer call() throws Exception {
// convert form parameters to customer object
}
}In this example we use an activator that gets FormParameters injected into its constructor and converts form parameters to Customer object.
https://github.com/bodar/utterlyidle/wiki/09.-Rendering
It's a common use case that some operation should be performed when the application starts. E.g. you may have a scheduler that should run on the application startup without any trigger from the client. StartupModule defines a start method where you can specify what action should be performed after the application starts.
public class JobsModule implements ResourcesModule, ApplicationScopedModule, RequestScopedModule, StartupModule {
public Resources addResources(Resources resources) throws Exception {
return resources.add(annotatedClass(BatchJobsResource.class));
}
public Container start(Container container){
BatchJobsResource batchJobsResource = container.get(BatchJobsResource.class);
batchJobsResource.start();
return container;
}
// more code
}Another common use case for a startup module may be a sending a warmup request:
import com.googlecode.totallylazy.Callable1;
import com.googlecode.utterlyidle.HttpHandler;
import com.googlecode.utterlyidle.RequestBuilder;
import com.googlecode.utterlyidle.Response;
import com.googlecode.utterlyidle.modules.StartupModule;
import com.googlecode.yadic.Container;
import static com.googlecode.totallylazy.Exceptions.ignoringException;
public class LessWarmupModule implements StartupModule {
@Override
public Container start(Container requestScope) {
final HttpHandler handler = requestScope.get(HttpHandler.class);
ignoringException(requestLessStyle()).apply(handler);
return requestScope;
}
private Callable1<HttpHandler, Response> requestLessStyle() {
return new Callable1<HttpHandler, Response>() {
@Override
public Response call(HttpHandler handler) throws Exception {
return handler.handle(RequestBuilder.get("stylesheets/style.less").build());
}
};
}
}TODO:
- ActivateSiteMeshModule
- SiteMeshModule
- AuditModule
- PerformanceModule
- ProfilingModule