+ All Categories
Home > Technology > Dropwizard

Dropwizard

Date post: 18-Jan-2017
Category:
Upload: scott-leberknight
View: 886 times
Download: 0 times
Share this document with a friend
78
Dropwizard Scott Leberknight 4/29/2016
Transcript

Dropwizard

Scott Leberknight4/29/2016

“…a Java framework for developing ops-friendly, high-performance, RESTful web

services…”

“…pulls together stable, mature libraries from the Java ecosystem into a simple, light-

weight package that lets you focus on getting things done…”

“…out-of-the-box support for sophisticated configuration, application metrics, logging,

operational tools…”

“…allowing you and your team to ship a production-quality web service in the shortest

time possible”

Key Takeaways…

Simple to Build

Simple to Deploy

Simple to Monitor

RESTful web services that are…

Main Components

Jetty - Standalone web server

Jackson - JSON processing

Jersey (JAX-RS) - REST

Metrics - Application metrics

Other components…

Hibernate Validator - Validation

Logback & SLF4J - Logging

Liquibase - Database migrations

JDBI or Hibernate - Database access

And more…

Google Guava - Utilities

HttpClient or Jersey Client - HTTP clients

Joda Time - Date/time API (*)

(*) Java 8 Date/Time API supersedes Joda

Freemarker & Mustache - Views/templates

Dropwizard app 30,000 foot view

Configuration

Application (Jetty server)

Resources (Jersey)

Data access (DAOs)

POJOs

reads

registers

accessserialize/ deserialize

JSON

CRUD

(from YAML file)

(using Jackson)

components

basic workflow

HTTPRequest

IncomingPOJO

Resource method

Logic, data access, etc.

OutgoingPOJO(s)

HTTPResponse

Deserialize JSON

SerializeJSON

CLIENT

The Players

Class/Component Description

ConfigurationApplication configuration de-serialized from YAML file (via Jackson-annotated class)

ApplicationInitialize bundles, commands, resources, health checks, etc. Starts Jetty server.

Resource Annotated Jersey/JAX-RS classes

DAOData access layer. Native Hibernate (AbstractDAO) and JDBI support.

HealthCheckDefines a runtime test of application behavior, dependencies, etc.

Managed Objects Components requiring an explicit lifecycle (start / stop)

CommandDefine command line actions (e.g. server starts Jetty; db handles database operations)

POJOs (aka Representations)Objects representing a service's RESTful API, serialized/deserialized by Jackson

Lets’ build a simple RESTful service to

control Nest thermostats…

Quick Digression - Lombok

“Project Lombok makes java a spicier language by adding ‘handlers’ that know

how to build and compile simple, boilerplate-free, not-quite-java code.”

- projectlombok.org

Lombok example #1

@Getter@Setter@EqualsAndHashCode@ToString@RequiredArgsConstructor @AllArgsConstructor public class Person { private String firstName; private String lastName; private int age;}

or use @Data{

Lombok example #2

@Data@Builder @NoArgsConstructor public class Person { private String firstName; private String lastName; private int age;}

Lombok example #3

val p = Person.builder() .firstName("Bob") .lastName("Smith") .age(36) .build();

local variable type inference

configuration class

@Getter @Setter @ToString public class NestConfiguration extends Configuration { @JsonProperty("database") @Valid @NotNull private DataSourceFactory dataSourceFactory; }

config YAMLdatabase: driverClass: org.h2.Driver user: nest password: ${DEVIGNITION_DB_PWD:-DevIg$2016} url: jdbc:h2:tcp://localhost/~/db/nest validationQuery: "/* Nest DB Health Check */ SELECT 42" properties: hibernate.connection.charSet: UTF-8 hibernate.dialect: org.hibernate.dialect.H2Dialect hibernate.format_sql: true hibernate.show_sql: false logging: level: INFO loggers: com.devignition: DEBUG org.hibernate.SQL: DEBUG

Application

main() - starts app (Jetty server)

initialize() - bundles, configuration options, etc.

run() - register Jersey resources & more

application classpublic class NestApplication extends Application<NestConfiguration> {

public static void main(final String[] args) throws Exception { new NestApplication().run(args); }

@Override public String getName() { return “Nest Service"; }

@Override public void initialize(Bootstrap<NestConfiguration> bootstrap) { // initialize bundles, e.g. migrations, Hibernate, etc. }

@Override public void run(NestConfiguration configuration, Environment environment) { // register resources, health checks, metrics, etc. }}

initialize - env var substitution

// in Application#initialize()

bootstrap.setConfigurationSourceProvider( new SubstitutingSourceProvider( bootstrap.getConfigurationSourceProvider(), new EnvironmentVariableSubstitutor(false)));

# config.yml# Use DEVIGNITION_DB_PWD env var, or# default to DevIg$2016 if not exists (bash syntax)

password: ${DEVIGNITION_DB_PWD:-DevIg$2016}

Bundles

Re-usable functionality

Examples:

- Database migrations bundle

- Hibernate bundle

Migrations Bundle

Liquibase (out of box)

Add migrations bundle to application class

initialize - add a Bundle

// in Application#initialize()

bootstrap.addBundle(new MigrationsBundle<NestConfiguration>() { @Override public PooledDataSourceFactory getDataSourceFactory(NestConfiguration config) { return config.getDataSourceFactory(); } });

Liquibase migration

<databaseChangeLog> <changeSet id="001-create-nests-table" author="batman"> <createTable tableName="nests"> <column name="id" type="bigint" autoIncrement="true"> <constraints primaryKey="true" nullable="false"/> </column> <column name="location" type="varchar(256)"> <constraints nullable="false" unique="true"/> </column> <column name="temperature" type="int"> <constraints nullable="false"/> </column> <column name="mode" type="varchar(256)"> <constraints nullable="false"/> </column> </createTable> </changeSet></databaseChangeLog>

run - register a resource

@Overridepublic void run(NestConfiguration configuration, Environment environment) {

NestDao nestDao = new NestDao( hibernateBundle.getSessionFactory()); NestResource nestResource = new NestResource(nestDao); environment.jersey().register(nestResource);}

Hibernate Bundle creates…

- managed connection pool

- database health check

- managed & instrumented SessionFactory

Managed Objects

Objects that are tied to the application’s lifecycle (i.e. to Jetty lifecycle)

start() called before server starts

stop() called after server shuts down

Ideal for thread pools, scheduled executors, etc.

managed executor

// Create a Dropwizard-managed executor service

boolean useDaemon = true; ScheduledExecutorService executor = environment.lifecycle() .scheduledExecutorService("special-task-%d", useDaemon) .build(); executor.scheduleAtFixedRate(this::performTask, 1L, 5L, TimeUnit.MINUTES);

Health Checks

Runtime test of behavior, dependencies, etc.

Health checks should lightly test service dependencies, e.g. databases, downstream services, etc.

health check

public class NestServiceHealthCheck extends HealthCheck {

private NestService nestService;

public NestServiceHealthCheck(NestService service) { this.nestService = service; }

@Override protected Result check() throws Exception { NestServiceStatus status = nestService.currentStatus(); if (status.ok() { return Result.healthy(); } else { return Result.unhealthy(status.problems()); } }}

Representations

Generally…just POJOs

Use Hibernate Validator, e.g. @NotBlank

Optionally use advanced Jackson features, e.g. custom serializers or deserializers

Sprinkle in Jackson annotations, e.g. @JsonIgnore

representation (POJO)

@Data @Entity @Table(name = "nests") public class NestThermostat { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank @Length(max = 256) private String location; @NotNull @Min(60) @Max(90) private Integer temperature;

@NotNull @Enumerated(EnumType.STRING) private Mode mode;

@JsonIgnore private UUID internalGuid; }

Data Access / Application Logic

Use whatever makes sense for a given service

Difference services may use different data stores

- Postgres for relational data

- Neo4J for connected data

- MongoDB for document-oriented data

DAO (Hibernate)

public class NestDao extends AbstractDAO<NestThermostat> { public NestDao(SessionFactory sessionFactory) { super(sessionFactory); } public NestThermostat getById(long id) { return get(id); } public List<NestThermostat> getAll() { Criteria criteria = currentSession().createCriteria(getEntityClass()) .addOrder(Order.asc("location")); return list(criteria); } public long create(NestThermostat nest) { checkState(nest.getId() == null, "New nests cannot have an id"); return persist(nest).getId(); } public void update(NestThermostat nest) { checkState(nest.getId() != null, "Existing Nest must have an id"); persist(nest); } public void delete(long id) { currentSession().delete(getById(id)); } }

DAO (JDBI)

public interface NestDao { @SqlQuery("select * from nests order by location") @Mapper(NestMapper.class) ImmutableList<Nest> getAllNests(); @SqlQuery("select * from nests where location = :it") @Mapper(NestMapper.class) @SingleValueResult(Nest.class) Optional<Nest> getNest(@Bind String location); @GetGeneratedKeys @SqlUpdate("insert into nests (location, location_id)" + " values (:location, :locationId)") long createNest(@BindBean Nest nest); @SqlUpdate("delete from nests where location = :it") void deleteNest(@Bind String location); }

Resource classes

Mostly just Jersey (JAX-RS) resource classes

Dropwizard adds some additional features, e.g.

- validation with @Valid

- AbstractParam and implementations

- Metrics support, e.g. @Timed

resource class

@Path("/nests") @Produces(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON) public class NestResource { private NestDao nestDao; public NestResource(NestDao nestDao) { this.nestDao = nestDao; } // resource methods (see next slide)}

( dependency injection w/o a framework…amazing what constructors can do ! )

example resource methods

@GET @Path("/{id}") @Timed @ExceptionMetered @UnitOfWork public Optional<NestThermostat> getById(@PathParam("id") LongParam id) { return Optional.ofNullable(nestDao.getById(id.get())); } @POST @Timed @ExceptionMetered @UnitOfWork public Response addNest(@Valid NestThermostat nest) { long id = nestDao.create(nest); URI location = UriBuilder.fromResource(NestResource.class) .path("{id}").build(id); return Response.created(location) .entity(nest) .build(); }

Now let's put on our DevOps hat…

https://twitter.com/shu/status/575801992641056768

Fat JARs

Maven Shade plugin

Single executable "fat" JAR files

One easily deployable artifact

(*)

(*) POJJ - Plain Old Java JAR

<plugin> <artifactId>maven-jar-plugin</artifactId> <version>2.6</version> <configuration> <archive> <manifest> <addClasspath>true</addClasspath> <mainClass>${mainClass}</mainClass> <addDefaultImplementationEntries>true</addDefaultImplementationEntries> </manifest> </archive> </configuration> </plugin>

Versioned JARs

Add Implementation-Version to JAR manifest

Maven jar plugin

Manifest-Version: 1.0Implementation-Title: Nest Example ServiceImplementation-Version: 1.0.0Archiver-Version: Plexus ArchiverBuilt-By: sleberknImplementation-Vendor-Id: com.devignition

3. MANIFEST.MF (in JAR)

<groupId>com.devignition</groupId><artifactId>nest-service</artifactId><version>1.0.0</version><packaging>jar</packaging><name>Nest Example Service</name>

1. pom.xml (in project)

nest-service-1.0.0.jar 2. Build JAR file / maven-jar-plugin

Deployment steps

Build executable fat JAR

Run database migrations (if any)

Start application (embedded Jetty server)

Build JAR

$ mvn clean package

(*) Chicken & egg - database tests will fail without first migrating

Commands

$ java -jar nest-service-1.0.0.jar usage: java -jar nest-service-1.0.0.jar [-h] [-v] {server,check,db} ...

positional arguments: {server,check,db} available commands

optional arguments: -h, --help show this help message and exit -v, --version show the application version and exit

Check, Migrate & Run!

$ java -jar nest-service-1.0.0.jar check config.yml

$ java -jar nest-service-1.0.0.jar db migrate config.yml

$ java -jar nest-service-1.0.0.jar server config.yml

Jetty is now listening…

Dropwizard App(Jetty)

App Port8080

Admin Port 8081

GET my.nest.com:8080 /nests

GET my.nest.com:8081 /healthcheck

GET my.nest.com:8081 /metrics

GET my.nest.com:8081 /ping

GET my.nest.com:8081 /threads

GET my.nest.com:8080 /nests/2

Ping…

(HTTP) GET Our Nests

DevOps Menu

Check our health

Metrics (there are a LOT)

Console

Metrics reporting

Graphite

CSV files

Ganglia

Custom…SLF4J

Monitoring

Common OS monitoring tools

ps, top, htop, lsof, iostat, etc.

Common OS monitoring tools

ps, top, htop, lsof, iostat, etc.

Testing Dropwizard

Fixtures for unit testing representations

Easily unit test resources using mock DAOs

Excellent integration test support…

Prefer AssertJ fluent-assertions

Lots of unit tests….

Integration testing

ResourceTestRule

DropwizardClientRule

DropwizardAppRule

DropwizardTestSupport

ResourceTestRule

public class NestResourceIntegrationTest { private static final NestDao NEST_DAO = mock(NestDao.class); @ClassRule public static final ResourceTestRule RESOURCE = ResourceTestRule.builder() .addResource(new NestResource(NEST_DAO)) .build();

@After public void tearDown() { reset(NEST_DAO); }

@Test public void testGetNestById() { long id = 42; NestThermostat nest = newNest(id, "Kitchen", 72, Mode.COOL); when(NEST_DAO.getById(id)).thenReturn(nest); assertThat(RESOURCE.client() .target("/nests/42") .request() .get(NestThermostat.class)) .isEqualToComparingFieldByField(nest); }}

(in-memory Jersey server)

DropwizardAppRule

public class NestApplicationIntegrationTest {

@ClassRule public static final DropwizardAppRule<NestConfiguration> APP = new DropwizardAppRule<>(NestApplication.class, ResourceHelpers.resourceFilePath("test-app-config.yml"));

@Test public void testGetNest() { Client client = new JerseyClientBuilder(APP.getEnvironment()) .build("app test client"); String url = String.format("http://localhost:%d/nests", APP.getLocalPort()); Response response = client.target(url) .request() .get(); assertThat(response.getStatusInfo()).isEqualTo(Response.Status.OK); }}

(full integration test starts your entire app)

& more to explore…

Tasks

Authentication

Filters

Simple Views

Form Handling

Clients

Polymorphic Config

Logging HTTP/2

HTTPS/TLS

Wrap-up

Production-focused, RESTful web services

DevOps-friendly deployment & monitoring

Make your apps Twelve-Factor apps…

The Twelve-Factor App

Dropwizard apps have many characteristics of 12-Factor apps

http://12factor.net

Simple to Build

Simple to Deploy

Simple to Monitor

RESTful web services that are…

sample code available at:

https://github.com/sleberknight/dropwizard-devignition-2016

https://jersey.java.net/

References, Act I

http://www.dropwizard.io/

http://wiki.fasterxml.com/JacksonHome

http://www.eclipse.org/jetty/

https://dropwizard.github.io/metrics/

References, Act II

http://hibernate.org/validator/

http://www.jdbi.org/

http://www.liquibase.org/

http://www.joda.org/joda-time/

https://github.com/google/guava/wiki

Photo Attributionsnest thermostat - http://www.nest.com

bundle - https://www.flickr.com/photos/pembo1781/5477885038/

caduceus - https://www.flickr.com/photos/26672416@N00/4777701981/

DevOps hat - https://twitter.com/shu/status/575801992641056768

coffee in mason jar - https://www.flickr.com/photos/traveloriented/9757236883/

tux - http://powerpcaccess.blogspot.com/2013/03/linux-on-ppc-mac.html#.VyGHCBMrLdQ

(I own or took all other photos myself)

gunshow comic - http://gunshowcomic.com/316

half moon - https://www.flickr.com/photos/75929731@N08/7044973631/

My Info

sleberknight atfortitudetec.com

www.fortitudetec.com

@sleberknight

scott.leberknight atgmail

Dropwizard???

http://gunshowcomic.com/316


Recommended