LoginSignup
57
59

More than 5 years have passed since last update.

Spring BootとDropwizardのアプリコード比較

Last updated at Posted at 2014-06-29

DropwizardのSpring版とも言えるSpring BootとDropwizardをコードベースで比較してみます。
わかり易く比較したいので、パッケージ構成・命名などはDropwizardに合わせています。
また、Dropwizardと同じように書きたいので、Spring Bootで必須ではないjarも依存させています。

ソース中のコメントにDropwizardの場合を書いときます。

maven設定

コードの比較の記事にしたいので、Dropwizardのpom.xmlについては、ここでは書きません。
気になる方は以下の記事も参照ください。

以下はSpring Bootの場合の一部です

pom.xml
  <!-- Inherit defaults from Spring Boot -->
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>1.1.2.RELEASE</version>
  </parent>

  <dependencies>
<!-- tomcatからjettyに変更したい場合の記述。liquibaseを依存させたらtomcatで動作しなかったため -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>${boot.version}</version>
      <exclusions>
        <exclusion>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-jetty</artifactId>
      <version>${boot.version}</version>
    </dependency>

<!-- メトリックスを利用したい場合だけ -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-actuator</artifactId>
      <version>${boot.version}</version>
    </dependency>

<!-- DBを利用したい場合だけ -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
      <version>${boot.version}</version>
    </dependency>

<!-- freemarkerを利用したい場合だけ。これを依存させるとclasspath:/templates/ディレクトリがないとエラーになりました -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-freemarker</artifactId>
      <version>${boot.version}</version>
    </dependency>

<!-- 必須でない -->
    <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>17.0</version>
    </dependency>

<!-- liquibaseを利用したい場合だけ -->
    <dependency>
      <groupId>org.liquibase</groupId>
      <artifactId>liquibase-core</artifactId>
      <version>3.2.0</version>
    </dependency>

    <dependency>
      <groupId>com.h2database</groupId>
      <artifactId>h2</artifactId>
      <version>1.3.175</version>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <version>${boot.version}</version>
      <scope>test</scope>
    </dependency>

  </dependencies>

  <build>
    <resources>
      <resource>
       <directory>src/main/resources</directory>
        <includes>
          <include>**/*</include>
        </includes>
      </resource>
    </resources>  
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
・・・
    </plugins>
  </build>

Hello-World(基本)の比較

まずは RestなHello-Worldで比較します。
ソース中のコメント部分がDropwizardになります。

Applicationクラス

Dropwizardは、このクラスが色々な機能を追加する役割を持つので、記述量が多くなります。
Spring Bootでは、SpringApplicationクラスやDIでこのあたりをしています。

HelloWorldApplication.java
@Configuration
@EnableAutoConfiguration
@ComponentScan
@EnableConfigurationProperties
public class HelloWorldApplication {
//public class HelloWorldApplication extends Application<HelloWorldConfiguration> {

    public static void main(String[] args) throws Exception {
        SpringApplication.run(HelloWorldApplication.class, args);
        //new HelloWorldApplication().run(args);
    }

//  @Override
//  public String getName() {
//      return "hello-world";
//  }
//
//  @Override
//  public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
//  }
//
//  @Override
//  public void run(HelloWorldConfiguration configuration,
//          Environment environment) throws ClassNotFoundException {
//      final Template template = configuration.buildTemplate();
//      environment.jersey().register(new HelloWorldResource(template));
    }

Configurationクラス

prefix = "hello-world"は、example.ymlに記述しているキーです。
Spring Bootではこのprefixをymlに追加しないと起動しませんでした。

ちなみに起動のときにexample.ymlを指定しています。

$ java -jar target/spike-spring-boot-1.0-SNAPSHOT.jar --spring.config.location=example.yml

$ java -jar target/spike-dropwizard-1.0-SNAPSHOT.jar server example.yml
HelloWorldConfiguration.java
@ConfigurationProperties(prefix = "hello-world", ignoreUnknownFields = false)
@Component
public class HelloWorldConfiguration {
//public class HelloWorldConfiguration extends Configuration {
    @NotEmpty
    private String template;

    @NotEmpty
    private String defaultName = "Stranger";

//  @JsonProperty
    public String getTemplate() {
        return template;
    }

//  @JsonProperty
    public void setTemplate(String template) {
        this.template = template;
    }

//  @JsonProperty
    public String getDefaultName() {
        return defaultName;
    }

//  @JsonProperty
    public void setDefaultName(String defaultName) {
        this.defaultName = defaultName;
    }

//  public Template buildTemplate() {
        return new Template(template, defaultName);
    }
}
example.yml
hello-world:
    template: Hello, %s!
    defaultName: Stranger
#template: Hello, %s!
#defaultName: Stranger

Resourceクラス

POSTでのバリデータエラー(この場合contentに"hoge"のように4文字以上)の場合は以下の違いがあります。

HTTPステータスコードは

  • 422 <- Dropwizard
  • 400 <- Spring Boot

bodyは

  • {"errors":["content length must be between 0 and 3 (was hoge)"]} <- Dropwizard
  • {"timestamp":1403789235278,"status":400,"error":"Bad Request","exception":"org.springframework.web.bind.MethodArgumentNotValidException","message":"Validation failed for argument at index 0 in method: public void com.github.ko2ic.resources.HelloWorldResource.receiveHello(com.github.ko2ic.core.Saying), with 1 error(s): [Field error in object 'saying' on field 'content': rejected value [hoge]; codes [Length.saying.content,Length.content,Length.java.lang.String,Length]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [saying.content,content]; arguments []; default message [content],3,0]; default message [length must be between 0 and 3]] ","path":"/hello-world"} <- Spring Boot
HelloWorldResource.java
//@Path("/hello-world")
//@Produces(MediaType.APPLICATION_JSON)
@RestController
@RequestMapping(value = "/hello-world")
public class HelloWorldResource {
    private static final Logger LOGGER = LoggerFactory
            .getLogger(HelloWorldResource.class);

//  private final Template template;
//  private final AtomicLong counter;
//
//  public HelloWorldResource(Template template) {
//      this.template = template;
//      this.counter = new AtomicLong();
//  }

    @Autowired
    private HelloWorldConfiguration configuration;
    private final AtomicLong counter = new AtomicLong();

//  @GET
//  @Timed(name = "get-requests")
//  @CacheControl(maxAge = 1, maxAgeUnit = TimeUnit.DAYS)
//  public Saying sayHello(@QueryParam("name") Optional<String> name) {
//      return new Saying(counter.incrementAndGet(), template.render(name));
//  }
    @RequestMapping(method = RequestMethod.GET)
    public Saying sayHello(
            @RequestParam(value = "name", required = false) String name,
            HttpServletResponse response) {
        response.setHeader("Cache-Control", "max-age=86400");
        Template template = configuration.buildTemplate();
        return new Saying(counter.incrementAndGet(), template.render(Optional
                .fromNullable(name)));
    }

//  @POST
    @RequestMapping(method = RequestMethod.POST)
//  public void receiveHello(@Valid Saying saying) {
    public void receiveHello(@RequestBody @Valid Saying saying) {
        LOGGER.info("Received a saying: {}", saying);
    }
}

モデルクラス

Spring Bootでは@JsonPropertyは必要ありません。

Saying.java
public class Saying {
    private long id;

    @Length(max = 3)
    private String content;

    public Saying() {
    }

    public Saying(long id, String content) {
        this.id = id;
        this.content = content;
    }

//  @JsonProperty
    public long getId() {
        return id;
    }

//  @JsonProperty
    public String getContent() {
        return content;
    }

    @Override
    public String toString() {
        return String.format("id=%d content=%s", getId(), getContent());
    }
Template.java
public class Template {
    private final String content;
    private final String defaultName;

    public Template(String content, String defaultName) {
        this.content = content;
        this.defaultName = defaultName;
    }

    public String render(Optional<String> name) {
        return format(content, name.or(defaultName));
    }
}

DBアクセスの比較(Hibernate)

Applicationクラス

Dropwizardの場合は以下のような設定をする必要がありますが、Spring Bootは特に何も追記する必要はありません。

HelloWorldApplication.java
//  // hibernateを使うため
//  private final HibernateBundle<HelloWorldConfiguration> hibernateBundle = new HibernateBundle<HelloWorldConfiguration>(
//          Person.class) {
//      @Override
//      public DataSourceFactory getDataSourceFactory(
//              HelloWorldConfiguration configuration) {
//          return configuration.getDataSourceFactory();
//      }
//  };
//
//  @Override
//  public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
//      // liquibaseを使うため
//      bootstrap.addBundle(new MigrationsBundle<HelloWorldConfiguration>() {
//          @Override
//          public DataSourceFactory getDataSourceFactory(
//                  HelloWorldConfiguration configuration) {
//              return configuration.getDataSourceFactory();
//          }
//      });
//      bootstrap.addBundle(hibernateBundle);
//  }
//  @Override
//  public void run(HelloWorldConfiguration configuration,
//          Environment environment) throws ClassNotFoundException {
//      final PersonRepository repository = new PersonRepository(
//              hibernateBundle.getSessionFactory());
//      environment.jersey().register(new PeopleResource(repository));

Configurationクラス

Dropwizardの場合はDataSourceFactoryを使います。
Spring Bootの場合はDataSourceAutoConfiguration & DataSourcePropertiesに自動で設定されるので、設定ファイルに書くだけで設定されます。
Applicationクラスでの@EnableAutoConfigurationspring-boot-autoconfigure.jar/META-INF/spring.factoriesに記述されたクラスが自動的にロードされるようです。)

HelloWorldConfiguration.java
    // @Valid
    // @NotNull
    // private DataSourceFactory database = new DataSourceFactory();
    //
    // @JsonProperty("database")
    // public DataSourceFactory getDataSourceFactory() {
    // return database;
    // }
    //
    // @JsonProperty("database")
    // public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
    // this.database = dataSourceFactory;
    // }

Dropwizardの場合は、liquibaseは特にliqibase用に記述しません

src/main/resources/config/application.yml
spring:
    application:
        name: Hello World!
    datasource:
        driverClassName: org.h2.Driver
        url: jdbc:h2:target/example
        user: sa
        password: sa
    jpa:
        hibernate:
            ddl-auto: false
        database: H2
        show-sql: true
    freemarker:
        cache: false
# デフォルトは、classpath:/templates/
        templateLoaderPath: classpath:/views/  
liquibase:
    change-log: classpath:/migrations.xml
    drop-first: true
    enabled: false  

# dropwizardの場合は、hello-worldで利用したexample.ymlに追記

#database:
#  driverClass: org.h2.Driver
#  user: sa
#  password: sa
#  url: jdbc:h2:target/example
#  properties:
#    charSet: UTF-8
#    hibernate.dialect: org.hibernate.dialect.H2Dialect
#  maxWaitForConnection: 1s
#  validationQuery: "/* MyApplication Health Check */ SELECT 1"
#  minSize: 8
#  maxSize: 32
#  checkConnectionWhileIdle: false

Resourceクラス

意図的にHTTPステータスコード404などを返したい場合に特に以下の違いがあります。

  • DropwizardではNotFoundExceptionを投げるだけ
  • Spring Bootでは自作例外を作成して、そのクラスを@ExceptionHandlerでハンドリングする
PeopleResource.java
//@Path("/people")
//@Produces(MediaType.APPLICATION_JSON)
@RestController
@RequestMapping(value = "/people")
public class PeopleResource {

//  private final PersonRepository repository;
//  public PeopleResource(PersonRepository repository) {
//      this.repository = repository;
//  }

    @Autowired
    private PeopleRepository peopleRepository;

//  @POST
//  @UnitOfWork
    @RequestMapping(method = RequestMethod.POST)
    @Transactional
    //public Person createPerson(Person person) {
    public @ResponseBody
    Person createPerson(@RequestBody Person people) {
        return repository.create(person);
    }

//  @GET
//  @UnitOfWork
    @RequestMapping(method = RequestMethod.GET)
    @Transactional
    public List<Person> listPeople() {
        return repository.findAll();
    }

//  @GET
//  @UnitOfWork
//  @Path("/{personId}")
    @RequestMapping(value = "/{personId}", method = RequestMethod.GET)
    @Transactional
//  public Person getPerson(@PathParam("personId") LongParam personId) {
    public Person getPerson(@PathVariable("personId") Long personId) {
        final Optional<Person> person = repository.findById(personId.get());
        if (!person.isPresent()) {
            //throw new NotFoundException("Not Found Person");
            throw new PersonNotFoundException("Not Found Person");
        }
        return person.get();
    }

    @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "Not Found Person")
    @ExceptionHandler(PersonNotFoundException.class)
    public void notfound() {
        // Nothing to do
    }

    private static class PersonNotFoundException extends RuntimeException {
        public PersonNotFoundException(String message) {
            super(message);
        }
    }
}

Entityクラス

DropwizardもSpring Bootも同じ。
Spring Bootではカラム名をfullNameのようにキャメルケースにすると動作しませんでした。(Dropwizardは動作した)

Person.java
@Entity
@Table(name = "people")
@NamedQueries({ @NamedQuery(name = "com.github.ko2ic.core.People.findAll", query = "SELECT p FROM Person p") })
public class Person {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;

    @Column(name = "full_name", nullable = false)
    private String fullName;

    @Column(name = "job_title", nullable = false)
    private String jobTitle;

    public Person() {
    }

    public Person(long id, String fullName, String jobTitle) {
        this.id = id;
        this.fullName = fullName;
        this.jobTitle = jobTitle;
    }
// あとはgetter,setterなので省略
}

Repositoryクラス

DropwizardではAbstractDAOを利用する。
Spring BootではEntityManagerを扱う。これはDropwizardでGuiceを使ったときも似たようなクラスになりました。

//public class PersonRepository extends AbstractDAO<Person> {
//  public PersonRepository(SessionFactory factory) {
//      super(factory);
//  }
//
//  public Optional<Person> findById(Long id) {
//      return Optional.fromNullable(get(id));
//  }
//
//  public Person create(Person person) {
//      return persist(person);
//  }
//
//  public List<Person> findAll() {
//      return list(namedQuery("com.github.ko2ic.core.Person.findAll"));
//  }
//}

@Repository
public class PeopleRepository {

    @PersistenceContext
    private EntityManager entityManager;

    public Optional<Person> findById(Long id) {
        return Optional.fromNullable(entityManager.find(Person.class, id));
    }

    public Person create(Person person) {
        entityManager.persist(person);
        return person;
    }

    @SuppressWarnings("unchecked")
    public List<Person> findAll() {
        return entityManager.createNamedQuery(
                "com.github.ko2ic.core.People.findAll").getResultList();
    }
}

Liquibaseを利用

DropwizardもSpring Bootもchangelogファイルは同じです。

src/main/resource/migrations.xml
<?xml version="1.0" encoding="UTF-8"?>

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog
         http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">

    <changeSet id="1" author="ko2ic">
        <createTable tableName="people">
            <column name="id" type="bigint" autoIncrement="true">
                <constraints primaryKey="true" nullable="false" />
            </column>
            <column name="full_name" type="varchar(255)">
                <constraints nullable="false" />
            </column>
            <column name="job_title" type="varchar(255)" />
        </createTable>
    </changeSet>    
</databaseChangeLog>

Spring Bootでは設定でenabled: trueにすると起動するたびにテーブルがDropされて、migrateされます。
なので、 本番環境では注意 が必要です。
Liquibaseの他の機能をSpring Boot経由で利用できるかどうかは不明でした。
直接、Liquibaseを利用する必要があるかもしれません。

src/main/resources/config/application.yml
liquibase:
    change-log: classpath:/migrations.xml
    drop-first: true
    enabled: true

Dropwizardは以下の記事のようにDropwizard経由で様々なことができました。

テンプレートによる動的ファイル表示の比較(freemarker)

今回はfreemarkerですが、Spring Bootの場合は、velocity, thymeleafでもほとんど変わりません。

Applicationクラス

Dropwizardの場合は以下のような設定をする必要がありますが、Spring Bootは特に何も追記する必要はありません。

HelloWorldApplication.java
//  @Override
//  public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
//      ・・・
//      bootstrap.addBundle(new ViewBundle());
//  }
//  @Override
//  public void run(HelloWorldConfiguration configuration,
//          Environment environment) throws ClassNotFoundException {
//      ・・・
//      environment.jersey().register(new ViewResource());
//  }

Resourceクラス

Dropwizardの場合はViewを継承したクラスにViewで表示したい値を設定して返します。
Spring Bootの場合は、引数のMapクラスにViewで表示したい値を設定し、戻り値はテンプレートファイルの場所を指定します。

ViewResource.java
//@Path("/views")
@Controller
public class ViewResource {

    @Value("${spring.freemarker.templateEncoding:UTF-8}")
    private String charset;

//  @GET
//  @Produces("text/html;charset=UTF-8")
//  @Path("/freemarker")
//  public View freemarkerUTF8() {
//      return new View("/views/ftl/utf8.ftl", Charsets.UTF_8) {
//      };
//  }

    @RequestMapping(value = "/views/freemarker")
    public String freemarkerUTF8(Map<String, Object> model) {
        model.put("charset", charset);
        return "ftl/utf8";
    }
}

両方とも同じテンプレートファイルです。

src/main/resouces/views/ftl/utf8.ftl
<html>
<body>
<h1>This is an example of a freemarker</h1>
文字コード:${charset}
</body>
</html>

静的ファイル表示の比較

Applicationクラス

Dropwizardの場合は、AssetsBundleを登録するだけです。
Spring Bootの場合は、SpringBootServletInitializerを継承させて、configureメソッドをオーバーライドさせます。

HelloWorldApplication.java
//public class HelloWorldApplication {
public class HelloWorldApplication extends SpringBootServletInitializer {

//  public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) {
//      bootstrap.addBundle(new AssetsBundle());
//      ・・・
//  }

    @Override
    protected SpringApplicationBuilder configure(
            SpringApplicationBuilder application) {
        return application.sources(HelloWorldApplication.class);
    }

ここでの静的ファイルは以下です。

src/main/resources/static/assets/js/example.js
alert('sample');

http://localhost:8080/assets/js/example.js
で表示できることを想定しています。

Dropwizardの場合は、 src/main/resources/assets/js/example.js
Spring Bootの場合は、src/main/resources/static/assets/js/example.js
に置きます。

カスタムヘルスチェックの比較

Applicationクラス

Dropwizardの場合は以下のような設定をする必要がありますが、Spring Bootは特に何も追記する必要はありません。

HelloWorldApplication.java
//  @Override
//  public void run(HelloWorldConfiguration configuration,
//          Environment environment) throws ClassNotFoundException {
//      ・・・
//      environment.healthChecks().register("template",
//              new TemplateHealthCheck(template));

HealthCheckクラス

DropwizardとSpring Bootでインターフェイスは当然違いますが、似ています。

TemplateHealthCheck.java_
//public class TemplateHealthCheck extends HealthCheck {
//  private final Template template;
//
//  public TemplateHealthCheck(Template template) {
//      this.template = template;
//  }
//
//  @Override
//  protected Result check() throws Exception {
//      return Result.unhealthy(template.render(Optional.of("error")));
//  }
//}
@Component
public class TemplateHealthCheck implements HealthIndicator {

    @Autowired
    private HelloWorldConfiguration configuration;

    @Override
    public Health health() {
        String data = configuration.buildTemplate()
                .render(Optional.of("error"));
        return Health.down().withDetail("message", data).build();
    }
}
Drpwizardの場合
$ curl http://localhost:8081/healthcheck
{"deadlocks":{"healthy":true},"hibernate":{"healthy":true},"template":{"healthy":false,"message":"Hello, error!"}}

Spring Bootの場合
$ curl http://localhost:8081/health
{"status":"DOWN","templateHealthCheck":{"status":"DOWN","message":"Hello, error!"},"db":{"status":"UP","database":"H2","hello":1}}
57
59
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
57
59