Skip to content

stevensurgnier/dropwizard-clojure

Repository files navigation

dropwizard-clojure

dropwizard.io

Table of Contents

  1. Leiningen
  2. Creating a Configuration class
  3. Creating an Application class
  4. Creating a Representation class
  5. Creating a Resource class
  6. Creating a HealthCheck
  7. Building fat JARs
  8. Running your Application

Leiningen

[dropwizard-clojure/dropwizard-clojure "0.2.0"]

Creating a Configuration class

TodoConfiguration.java

package com.example.todo;

import com.fasterxml.jackson.annotation.JsonProperty;
import io.dropwizard.Configuration;
import javax.validation.constraints.NotNull;

public class TodoConfiguration extends Configuration {
    @NotNull
    private int maxSize;

    @JsonProperty
    public int getMaxSize() {
        return maxSize;
    }

    @JsonProperty
    public void setMaxSize(int maxSize) {
        this.maxSize = maxSize;
    }
}

Dropwizard will serialize a yml file into your configuration class before starting the application.

dev-todo.yml

maxSize: 4

Creating an Application class

Your dropwizard application requires a concrete implementation of io.dropwizard.Application that is parameterized with your configuration class from above. Unfortunately, clojure does not yet support specifying parameterized types. Thus, a tiny abstract application class is recommended to specify the parameterized configuration class.

AbstractTodoApplication.java

package com.example.todo;

import io.dropwizard.Application;

public abstract class AbstractTodoApplication
    extends Application<TodoConfiguration> {
}

Now we're ready to write some clojure.

The defapplication macro is provided to conveniently create proxy instances of your abstract application class. Two function signatures are available. One that allows you override the initialize and run methods. The other allows you to override the run method only. Below the latter is shown.

The defmain macro is provided to conveniently generate a main method that will start your application correctly.

core.clj

(ns com.example.todo.core
  (:require [dropwizard-clojure.core :refer [defapplication defmain]]
  (:import [com.example.todo AbstractTodoApplication TodoConfiguration]
           [io.dropwizard.setup Environment])
  (:gen-class))

(defapplication todo-app
  AbstractTodoApplication
  (fn [^TodoConfiguration config ^Environment env]
    ;; nothing to do yet
    ))

(defmain todo-app)

Creating a Representation class

Todo.java

package com.example.todo.representations;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.hibernate.validator.constraints.NotEmpty;
import javax.validation.constraints.NotNull;

public class Todo {
    @NotNull
    private Boolean complete;
    @NotEmpty
    private String description;

    public Todo() {
        // Jackson deserialization
    }

    public Todo(Boolean complete, String description) {
        this.complete = complete;
        this.description = description;
    }

    @JsonProperty
    public Boolean getComplete() {
        return complete;
    }

    @JsonProperty
    public String getDescription() {
        return description;
    }
}

Creating a Resource class

core.clj

(ns com.example.todo.resources.todo
  (:require [clojure.tools.logging :as log])
  (:import [com.example.todo.representations Todo]
           [com.codahale.metrics.annotation Timed]
           [javax.validation Valid]
           [javax.ws.rs GET POST DELETE Path Consumes Produces PathParam]))

(definterface ITodo
  (get [])
  (delete [])
  (add [^Long id ^com.example.todo.representations.Todo todo])
  (get [^Long id])
  (toggle [^Long id])
  (delete [^Long id]))

(deftype ^{Path "/todo"
           Consumes ["application/json"]
           Produces ["application/json"]}
    TodoResource [state]
    ITodo
    (^{GET true Timed true} get [this] @state)

    (^{DELETE true Timed true} delete [this]
     (reset! state (atom {}))
     {})
    
    (^{Path "{id}" POST true Timed true}
     add [this ^{PathParam "id"} id ^{Valid true} todo]
     (swap! state assoc id todo))
    
    (^{Path "{id}" GET true Timed true}
     get [this ^{PathParam "id"} id]
     (get @state id))
    
    (^{Path "{id}/toggle" POST true Timed true}
     toggle [this ^{PathParam "id"} id]
     (swap! state update-in [id]
       #(Todo. (not (.getComplete %)) (.getDescription %))))

    (^{Path "{id}" DELETE true Timed true}
     delete [this ^{PathParam "id"} id]
     (swap! state dissoc id)))

(defn todo-resource []
  (TodoResource. (atom {})))

Registering a Resource

core.clj

(ns com.example.todo.core
  (:require [dropwizard-clojure.core :refer [defapplication defmain register-resource]]
            [com.example.todo.resources.todo :refer [todo-resource]]
  (:import [com.example.todo AbstractTodoApplication TodoConfiguration]
           [io.dropwizard.setup Environment])
  (:gen-class))

(defapplication todo-app
  AbstractTodoApplication
  (fn [^TodoConfiguration config ^Environment env]
    (register-resource (todo-resource))))

(defmain todo-app)

The register-resources accepts of a list of resources and returns the environment to allow threading.

Creating a HealthCheck

Functions may be registered to check the health of your application. The value returned from the function may optionally include a message or Throwable. If the value returned is a sequence, vector, or list, the first element is checked for truthiness to represent the health and the second element is the message or Throwable. Otherwise, the value returned is simply checked for truthiness to represent the health.

todo_size.clj

(ns com.example.todo.health.todo-size
  (:import [com.example.todo.resources.todo TodoResource]))

(defn todo-size [max-size ^TodoResource resource]
  (if (<= (count (.get resource)) max-size)
    true
    [false "too many todos"]))

Registering a HealthCheck

core.clj

(ns com.example.todo.core
  (:require [dropwizard-clojure.core
             :refer [defapplication defmain register-resource
                     register-healthcheck]]
            [com.example.todo.resources.todo :refer [todo-resource]]
            [com.example.todo.health.todo-size :refer [todo-size]])
  (:import [com.example.todo AbstractTodoApplication TodoConfiguration]
           [io.dropwizard.setup Environment])
  (:gen-class))

(defapplication todo-app
  AbstractTodoApplication
  (fn [^TodoConfiguration config ^Environment env]
    (let [resource (todo-resource)
          healthcheck #(todo-size (.getMaxSize config) resource)]
      (-> env
          (register-resource resource)
          (register-healthcheck :todo-size healthcheck)))))

(defmain todo-app)

The register-healthchecks accepts of map of healthcheck name to healthcheck function and returns the environment to allow threading.

Building fat JARs

lein uberjar

Running your Application

lein run server resources/dev-todo.yml

or

java -jar target/dropwizard-clojure-example-0.1.0-SNAPSHOT-standalone.jar server resources/dev-todo.yml