Date post: | 18-Jan-2017 |
Category: |
Technology |
Upload: | scott-leberknight |
View: | 886 times |
Download: | 0 times |
“…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”
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
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
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
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}
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(); }
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)
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
Testing Dropwizard
Fixtures for unit testing representations
Easily unit test resources using mock DAOs
Excellent integration test support…
Prefer AssertJ fluent-assertions
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
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