microservices to-go mit dropwizardmicroservices to-go mit dropwizard java forum stuttgart 2017 mario...
TRANSCRIPT
Microservices To-Gomit Dropwizard
Java Forum Stuttgart 2017
Mario GollerSoftware Engineer, Swisscom AG
Microservices To-Go
Excursion: Architecture Comparison
Monolith: multiple modules in the same container / processMicroservices: Modules running in di!erent processes
“ Dropwizard is a Java
framework for developing ops-
friendly, high-performance,
RESTful web services
http://www.dropwizard.io/
Created by YammerYammer was migrating IT infrastructure to mircoservices ->common patterns where extracted in one frameworkFirst Release: 2011-12-22 ( )Independent project since 2014 ( )Current Release: Dropwizard 1.1.2
Dropwizard 0.1.0Dropwizard 0.7.0
History
How the Dropwizard Frameworkcan help us to easily build
microservices ...
“ Dropwizard ... pulls together stable,
mature libraries from the Java
ecosystem into a simple, light-
weight package that lets you focus on
getting things done
http://www.dropwizard.io/
for HTTP servin' for REST modelin'
for JSON parsin' and generatin' for loggin'
for validatin' for "gurin' out what your application is
doin' in production and for databasin'
for migratin'
JettyJerseyJacksonLogbackHibernate ValidatorMetrics
JDBI HibernateLiquibase
Dropwizard's Library set
How to get started ...?
Use existing Maven archetype to generate simple Dropwizard projecthttps://github.com/nicktelford/dropwizard/tree/feature/maven-archetypes
this is setting up the required base implementation (already executable)
mvn archetype:generate -DarchetypeGroupId=io.dropwizard.archetypes -DarchetypeArtifactId=java-simple -DarchetypeVersion=1.1.2
Dropwizard Components Overview
main() - starts app (Jetty server)initialize() - bundles, con"guration options, etc.run() - register Jersey resources & servlet "lters and more
public class MyApplication extends Application<MyConfiguration> {
public static void main(String[] args) throws Exception { new MyApplication().run(args); }
@Override public void initialize(Bootstrap<AddressModuleConfiguration> bootstrap) { }
@Override public void run(final AddressModuleConfiguration configuration, final Environment environment) throws Exception { environment.jersey().register(...); environment.lifecycle().manage(...); environment.healthChecks().register(...); environment.admin().addTask(...); }}
Application
server:applicationContextPath: /myApplication
rootPath: /rest applicationConnectors: - type: http port: 8080 adminConnectors: - type: http port: 8082helloMessage: 'Hello Dropwizard!'logging: level: INFO appenders: - type: console threshold: ALL target: stdout
Configurationpublic class MyConfiguration extends Configuration { @NotBlank private String helloMessage;
public void setCustomSetting(String message) { this.helloMessage = message; }
public String getHelloMessage() { return helloMessage; }}
Con"guration "le (Yaml)##
helloMessage: 'Hello Dropwizard!'logging:
@Path(value = "/hello")@Produces(MediaType.TEXT_PLAIN)public class HelloResource {
private final MyConfiguration conf;
public HelloResource(MessagesConfiguration conf) { this.conf = conf; }
@GET public String sayHello() { return conf.getHelloMaessage(); }
}
Creating a REST Resource
using "plain" JAX-RS 2.0 to create resources or providers
How it works together
public class MyApplication extends Application<MyConfiguration> {
public static void main(String[] args) throws Exception { new MyApplication().run(args); }
@Override public String getName() { return "My application"; }
@Override public void initialize(Bootstrap<AddressModuleConfiguration> bootstrap) { bootstrap.addBundle(new AssetsBundle("/frontend-assets/", "/", "index.html", "frontend-assets")); bootstrap.addBundle(new WeldBundle()); }
@Override public void run(final AddressModuleConfiguration configuration, final Environment environment) throws Exception {
environment.jersey().register(ConstraintViolationExceptionMapper.class); environment.jersey().register(new JsonProcessingExceptionMapper()); environment.jersey().packages("org.jfs.dw.rest");
environment.servlets().addFilter("MdcLoggingServletFilter",MdcLoggingServletFilter.class) .addMappingForUrlPatterns(of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.ASYNC), false, "/*"); environment.servlets().addServletListeners(new RequestResponseHolderListener()); }}
Full Application Class Example
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <configuration> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>org.jfs.dw.MyApplication</mainClass> </transformer> </transformers> </configuration> </execution> </executions></plugin>
Build the Application Fat Jar with Maven
$ mvn package
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-enforcer-plugin</artifactId> <configuration> <rules> <DependencyConvergence/> </rules> </configuration> <executions> <execution> <id>enforce</id> <configuration> <rules> <DependencyConvergence/> </rules> </configuration> <goals> <goal>enforce</goal> </goals> <phase>package</phase> </execution> </executions> </plugin>
Use also maven enforcer plugin !
java -jar myApplication.jar server myConfig.yaml
INFO org.eclipse.jetty.util.log - Logging initialized @1780msINFO io.dropwizard.server.DefaultServerFactory - Registering jersey handler with root path prefix: /myappINFO io.dropwizard.server.DefaultServerFactory - Registering admin handler with root path prefix: /INFO io.dropwizard.assets.AssetsBundle - Registering AssetBundle with name: frontend-assets for path /*INFO io.dropwizard.server.DefaultServerFactory - Registering jersey handler with root path prefix: /myappINFO io.dropwizard.server.DefaultServerFactory - Registering admin handler with root path prefix: /INFO io.dropwizard.server.ServerFactory - Starting My fancy Application
INFO org.eclipse.jetty.setuid.SetUIDListener - Opened application@4905c46b{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}INFO org.eclipse.jetty.setuid.SetUIDListener - Opened application@17ae7628{SSL,[ssl, http/1.1]}{0.0.0.0:8443}INFO org.eclipse.jetty.setuid.SetUIDListener - Opened admin@1136b469{HTTP/1.1,[http/1.1]}{0.0.0.0:8082}INFO org.eclipse.jetty.setuid.SetUIDListener - Opened admin@6579c3d9{SSL,[ssl, http/1.1]}{0.0.0.0:8444}INFO org.eclipse.jetty.server.Server - jetty-9.3.9.v20160517INFO org.eclipse.jetty.server.handler.ContextHandler - Started i.d.j.MutableServletContextHandler@7a364e1c{/,null,AVAILABLE}INFO org.eclipse.jetty.server.AbstractConnector - Started application@4905c46b{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}INFO org.eclipse.jetty.server.AbstractConnector - Started application@17ae7628{SSL,[ssl, http/1.1]}{0.0.0.0:8443}INFO org.eclipse.jetty.server.AbstractConnector - Started admin@1136b469{HTTP/1.1,[http/1.1]}{0.0.0.0:8082}INFO org.eclipse.jetty.server.AbstractConnector - Started admin@6579c3d9{SSL,[ssl, http/1.1]}{0.0.0.0:8444}
Running the Application Jar
Two arguments are needed in order to run the JAR“server” which instructs Dropwizard to run as server.path to YAML con"guration "le.
Bundles
reusable group of functionality, used to de"ne blocks of anapplication’s behavior
public interface Bundle { void initialize(Bootstrap<?> bootstrap); void run(Environment environment);}
public interface ConfiguredBundle<T> { void initialize(Bootstrap<?> bootstrap); void run(T configuration, Environment environment) throws Exception;}
@Overridepublic void initialize(final Bootstrap<BeachBarClientConfiguration> bootstrap) { VersionSupplier supplier = new MavenVersionSupplier("org.jfs.dw", "my-appication"); bootstrap.addBundle(new VersionBundle(supplier));}
Using Databases
you can chose your preferred API:Hibernate ORMJDBI Data Access
#DataSource con"guration via YAML
database: driverClass : org.postgresql.Driver url: 'jdbc:postgresql://db.example.com/db-prod' user: pg-user password: iAMs00perSecrEET
JDBI
the "dropwizard-jdbi" module allows us to create a databaseconnections and Data Access Objects (DAO) through which we willquery the databasemaking use of the API provided by the JDBI project ( )
Example:
http://jdbi.org/
@Overridepublic void run(ExampleConfiguration config, Environment environment) { final DBIFactory factory = new DBIFactory(); final DBI jdbi = factory.build(environment, config.getDataSourceFactory(), "postgresql"); final UserDAO dao = jdbi.onDemand(UserDAO.class); environment.jersey().register(new UserResource(dao));}
public interface UserDAO {
@SqlUpdate("insert into users (sessionid, name) values (:sessionId, :name)") @GetGeneratedKeys int insert(@Bind("sessionId") String sessionId, @Bind("name") String name);
@MapResultAsBean @SqlQuery("select * from users") List<User> getAllUsers();}
Managed Objects (Example MongoDB)
public class App extends Application<AppConfiguration> {
@Override public void run(AppConfiguration config, Environment environment) { ManagedMongoClient mongoClient = config.getMongo().build(); environment.lifecycle().manage(mongoClient); DB db = mongoClient.getDB(config.getMongo().getDbName()); } }
public class ManagedMongoClient extends MongoClient implements Managed {
public ManagedMongoClient(MongoClientURI uri) throws UnknownHostException { super(uri); }
@Override public void start() throws Exception { }
@Override public void stop() throws Exception { close(); }
}
Operations friendly
HealthchecksCommands (executable actions via CL)Tasks (executable actions via HTTP)Metrics (via )Easy con"gurable Logging (Logback,SLF4J)
Dropwizard Metrics
Dropwizard provides some handy features to support operational work
Application Healthchecks
Healthchecks
a runtime test which you can use to verify your application’sbehaviour, depencencies etc. in its environmentHealth checks should lightly test service dependencies, e.g.databases, downstream services, etc.
$ curl http://dw.example.com:8081/healthcheck
{"deadlocks":{"healthy":true},"database":{"healthy":true}}
public class DatabaseHealthCheck extends HealthCheck {
private final HibernateBundle hibernate; public DatabaseHealthCheck(HibernateBundle hibernate) { super("database"); this.hibernate = hibernate; } @Override protected Result check() throws Exception { if(!hibernate.getSessionFactory().isClosed()) { return Result.healthy(); } else { return Result.unhealthy("Cannot connect to database"); } }}
Healthcheck Class Example
environment.addHealthCheck(new DatabaseHealthCheck(hibernate));
adding the healthcheck to the application
Deployment into the Cloud
Example of deploying a Dropwizard App to theHeroku Cloud
web: java $JAVA_OPTS -Ddw.http.port=$PORT -Ddw.http.adminPort=$PORT -jar target/myproject-1.0-SNAPSHOT.jar server config.yaml
heroku create
git push heroku master
-----> Discovering process types Procfile declares types -> web-----> Compiled slug size: 57.7MB-----> Launching... done, v9 http://myproject.herokuapp.com deployed to Heroku
Metrics
http://metrics.dropwizard.io
almost all applications need the ability to report key statisticsmetrics library written by Dropwizard teamsupports Meters, Gauges, Histograms, and Timers
Monitoring a Dropwizard Application(visualize pushed metrics)
"dropwizard-testing" module can be easily used for testingyour application representation classes and resource classesFixtures for unit testing representationsEasily unit test resources using mock DAOsExcellent integration test support…Prefer AssertJ $uent-assertionsJUnit rule for full-stack testing of your entire app
!ResourceTestRule
DropwizardClientRule
DropwizardAppRule
DropwizardTestSupport
Testing a Dropwizard Application
public class HelloResourceTest {
@Rule public ResourceTestRule resource = ResourceTestRule.builder() .addResource(new HelloResource()).build();
@Test public void testGetGreeting() { String expected = "Hello world!"; //Obtain client from @Rule. Client client = resource.client(); //Get WebTarget from client using URI of root resource. WebTarget helloTarget = client.target("http://localhost:8080/hello"); //To invoke response we use Invocation.Builder //and specify the media type of representation asked from resource. Invocation.Builder builder = helloTarget.request(MediaType.TEXT_PLAIN); //Obtain response. Response response = builder.get();
//Do assertions. assertEquals(Response.Status.OK, response.getStatusInfo()); String actual = response.readEntity(String.class); assertEquals(expected, actual);
}}
Integration Test Example
Dropwizard vs. Spring Boot
Main di!erence is dependency injection supportSpring’s core comes with it’s built in dependency injection supportDropwizard doesn’t come out of the box with DIDropwizard o!ers integrations with other DI frameworks like Guice, CDI…
both frameworks have a special module for testing including JUnit andMockito dependencies.Spring Boot is a part of Spring ecosystem and suits best for Spring-oriented projectsSpring o!ers broader list of supported services - REST, JMS, Messaging...Dropwizard owns popular metrics library
Dropwizard vs. Spring Boot
Anything else?
Additional Dropwizard Modulesand Tooling
http://modules.dropwizard.io/
https://github.com/Tasktop/dropwizard-tools
Dropwizard and Dependency Injection
only Jersey H2K dependency injection support out-of-the-boxuseful for simple casesfor more features a existing DI framework must be included
Google GuiceWeld CDISpring DI
@Overridepublic void run(ExampleServiceConfiguration conf, Environment env) throws Exception { Injector injector = Guice.createInjector(); env.addResource(injector.getInstance(HelloResource.class));}
an additional "H2K-bridge" is needed to avoid clashes in DI<dependency> <groupId>org.glassfish.hk2</groupId> <artifactId>guice-bridge</artifactId> <version>2.4.0-b31</version></dependency>
Database Migrationwith Liquibase
Validation
View Templates
Authentication
Questions