作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
Dusan Simonovic's profile image

Dusan Simonovic

Dusan是一名后端Java开发人员,拥有8年的Java开发经验,参与过许多大型项目.

Expertise

Previously At

InterVenture
Share

我们都见证了…越来越受欢迎 microservice architectures. 在微服务架构中,Dropwizard占有非常重要的地位. 它是用于构建RESTful web服务或web服务的框架, more precisely, 一组用于构建RESTful web服务的工具和框架.

它允许开发人员快速启动项目. 这可以帮助您打包应用程序,以便作为独立服务轻松地部署到生产环境中. 如果你曾经在需要启动项目的情况下 Spring framework例如,你可能知道它有多痛苦.

插图:Dropwizard教程中的微服务示例.

使用Dropwizard,只需添加一个Maven依赖项即可.

In this blog, 我将指导您完成编写简单Dropwizard RESTful服务的完整过程. 完成之后,我们将拥有一个服务,用于“部件”上的基本CRUD操作.” It doesn’t really matter what “part” is; it can be anything. It just came to mind first.

We will store the data in a MySQL database, using JDBI for querying it, and will use following endpoints:

  • GET /parts -to retrieve all parts from DB
  • GET /part/{id} to get a particular part from DB
  • POST /parts -to create new part
  • PUT /parts/{id} -to edit an existing part
  • DELETE /parts/{id} -to delete the part from a DB

我们将使用OAuth来验证我们的服务,最后,向它添加一些单元测试.

Default Dropwizard Libraries

而不是包括单独构建REST服务所需的所有库并配置每个库, Dropwizard does that for us. 以下是默认Dropwizard自带的库列表:

  • Jetty: 运行web应用程序需要HTTP. Dropwizard嵌入了Jetty servlet容器来运行web应用程序. 而不是将应用程序部署到应用程序服务器或web服务器, Dropwizard定义了一个main方法,该方法将Jetty服务器作为一个独立的进程调用. As of now, Dropwizard recommends only running the application with Jetty; other web services like Tomcat are not officially supported.
  • Jersey: Jersey是市场上最好的REST API实现之一. Also, 它遵循标准JAX-RS规范, 它是JAX-RS规范的参考实现. Dropwizard使用Jersey作为构建RESTful web应用程序的默认框架.
  • Jackson: Jackson是JSON格式处理的事实上的标准. 它是JSON格式最好的对象映射器api之一.
  • Metrics: Dropwizard有自己的指标模块,用于通过HTTP端点公开应用程序指标.
  • Guava: 除了高度优化的不可变数据结构, Guava提供了越来越多的加速类 development in Java.
  • Logback and Slf4j: 这两种方法用于更好的日志记录机制.
  • Freemarker and Mustache: 为应用程序选择模板引擎是关键决策之一. 所选择的模板引擎必须更加灵活,以便编写更好的脚本. Dropwizard使用著名和流行的模板引擎Freemarker和Mustache来构建用户界面.

Apart from the above list, 还有许多像Joda Time这样的库, Liquibase, Apache HTTP Client, 以及由Dropwizard用于构建REST服务的Hibernate验证器.

Maven Configuration

Dropwizard officially supports Maven. Even if you can use other build tools, 大多数指南和文档都使用Maven, so we’re going to use it too here. 如果您不熟悉Maven,可以看看这个 Maven tutorial.

这是创建Dropwizard应用程序的第一步. 请在您的Maven中添加以下条目 pom.xml file:


  
    io.dropwizard
    dropwizard-core
    ${dropwizard.version}
  

在添加上述条目之前,您可以添加 dropwizard.version as below:


  1.1.0

That’s it. 您已经完成了Maven配置的编写. 这将把所有必需的依赖项下载到项目中. The current Dropwizard version is 1.1.0, so we will be using it this guide.

现在,我们可以继续编写第一个真正的Dropwizard应用程序.

Define Configuration Class

Dropwizard stores configurations in YAML files. You will need to have the file configuration.yml in your application root folder. 然后将该文件反序列化为应用程序配置类的实例并进行验证. 应用程序的配置文件是Dropwizard的配置类(io.dropwizard.Configuration).

Let’s create a simple configuration class:

import javax.validation.Valid;
import javax.validation.constraints.NotNull;

import com.fasterxml.jackson.annotation.JsonProperty;

import io.dropwizard.Configuration;
import io.dropwizard.db.DataSourceFactory;

公共类dropwizard dblogconfiguration扩展Configuration {
  private static final String DATABASE = " DATABASE ";

  @Valid
  @NotNull
  private DataSourceFactory = new DataSourceFactory();

  @JsonProperty(DATABASE)
  getDataSourceFactory() {
    return dataSourceFactory;
  }

  @JsonProperty(DATABASE)
  setDataSourceFactory(最终DataSourceFactory) {
    this.dataSourceFactory = dataSourceFactory;
  }
}

YAML配置文件看起来像这样:

database:
  driverClass: com.mysql.cj.jdbc.Driver
  url: jdbc: mysql: / / localhost / dropwizard_blog
  user: dropwizard_blog
  password: dropwizard_blog 
  maxWaitForConnection: 1s
  validationQuery: "SELECT 1"
  validationQueryTimeout: 3s
  minSize: 8
  maxSize: 32
  checkConnectionWhileIdle: false
  evictionInterval: 10s
  minIdleTime: 1 minute
  checkConnectionOnBorrow: true

上述类将从YAML文件中反序列化,并将YAML文件中的值放入该对象.

Define an Application Class

现在我们应该去创建主应用程序类. 这个类将把所有的包放在一起,启动应用程序并使其运行.

下面是Dropwizard中一个应用程序类的例子:

import io.dropwizard.Application;
import io.dropwizard.auth.AuthDynamicFeature;
import io.dropwizard.auth.oauth.OAuthCredentialAuthFilter;
import io.dropwizard.setup.Environment;

import javax.sql.DataSource;

import org.glassfish.jersey.server.filter.RolesAllowedDynamicFeature;
import org.skife.jdbi.v2.DBI;

import com.toptal.blog.auth.DropwizardBlogAuthenticator;
import com.toptal.blog.auth.DropwizardBlogAuthorizer;
import com.toptal.blog.auth.User;
import com.toptal.blog.config.DropwizardBlogConfiguration;
import com.toptal.blog.health.DropwizardBlogApplicationHealthCheck;
import com.toptal.blog.resource.PartsResource;
import com.toptal.blog.service.PartsService;

public class DropwizardBlogApplication extends Application {
  private static final String SQL = "sql";
  private static final String DROPWIZARD_BLOG_SERVICE = "Dropwizard博客服务";
  private static final String BEARER = " BEARER ";

  public static void main(String[] args)抛出异常{
    new DropwizardBlogApplication().run(args);
  }

  @Override
  公共无效运行(dropwizard dblogconfiguration配置,环境环境){
    // Datasource configuration
    final DataSource dataSource =
        configuration.getDataSourceFactory().build(environment.metrics(), SQL);
    DBI dbi = new DBI(dataSource);

    // Register Health Check
    dropwizard dblogapplicationhealthcheck healthCheck =
        新DropwizardBlogApplicationHealthCheck (dbi.onDemand(PartsService.class));
    environment.healthChecks().注册(DROPWIZARD_BLOG_SERVICE healthCheck);

    // Register OAuth authentication
    environment.jersey()
        .新增OAuthCredentialAuthFilter.Builder()
            .setAuthenticator(新DropwizardBlogAuthenticator ())
            .setAuthorizer(新DropwizardBlogAuthorizer ()).setPrefix(BEARER).buildAuthFilter()));
    environment.jersey().register(RolesAllowedDynamicFeature.class);

    // Register resources
    environment.jersey().register(new PartsResource(dbi.onDemand(PartsService.class)));
  }
}

上面实际做的是覆盖Dropwizard运行方法. In this method, we’re instantiating a DB connection, 注册自定义运行状况检查(稍后讨论), 初始化服务的OAuth身份验证, and finally, registering a Dropwizard resource.

All of these will be explained later on.

Define a Representation Class

现在我们必须开始考虑我们的REST API以及我们的资源的表示形式. 我们必须设计JSON格式和相应的表示类,将其转换为所需的JSON格式.

让我们来看看这个简单表示类示例的JSON格式:

{
  "code": 200,
  "data": {
    "id": 1,
    "name": "Part 1",
    "code": "PART_1_CODE"
  }
}

对于上述JSON格式,我们将创建如下表示类:

import org.hibernate.validator.constraints.Length;

import com.fasterxml.jackson.annotation.JsonProperty;

public class Representation {
  private long code;

  @Length(max = 3)
  private T data;

  public Representation() {
    // Jackson deserialization
  }

  public Representation(long code, T data) {
    this.code = code;
    this.data = data;
  }

  @JsonProperty
  public long getCode() {
    return code;
  }

  @JsonProperty
  public T getData() {
    return data;
  }
}

This is fairly simple POJO.

Defining a Resource Class

资源就是REST服务的全部内容. 它只是一个端点URI,用于访问服务器上的资源. 在这个例子中,我们将有一个资源类,它带有一些用于请求URI映射的注释. 由于Dropwizard使用JAX-RS实现,因此我们将使用 @Path annotation.

下面是我们Dropwizard示例的一个资源类:

import java.util.List;

import javax.annotation.security.RolesAllowed;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.jetty.http.HttpStatus;

import com.codahale.metrics.annotation.Timed;
import com.toptal.blog.model.Part;
import com.toptal.blog.representation.Representation;
import com.toptal.blog.service.PartsService;

@Path("/parts")
@Produces(MediaType.APPLICATION_JSON)
@RolesAllowed("ADMIN")
public class PartsResource {
  private final PartsService partsService;;

  public PartsResource(PartsService) {
    this.partsService = partsService;
  }

  @GET
  @Timed
  public Representation> getParts() {
    return new Representation>(HttpStatus.OK_200, partsService.getParts());
  }

  @GET
  @Timed
  @Path("{id}")
  public Representation getPart(@PathParam("id") final int id) {
    return new Representation(HttpStatus.OK_200, partsService.getPart(id));
  }

  @POST
  @Timed
  public Representation createPart(@NotNull @Valid final Part part) {
    return new Representation(HttpStatus.OK_200, partsService.createPart(part));
  }

  @PUT
  @Timed
  @Path("{id}")
  public Representation editPart(@NotNull @Valid final Part part,
      @PathParam("id") final int id) {
    part.setId(id);
    return new Representation(HttpStatus.OK_200, partsService.editPart(part));
  }

  @DELETE
  @Timed
  @Path("{id}")
  public Representation deletePart(@PathParam("id") final int id) {
    return new Representation(HttpStatus.OK_200, partsService.deletePart(id));
  }
}

你可以看到所有的端点都是在这个类中定义的.

Registering a Resource

我现在回到主应用类. 您可以在该类的末尾看到,我们已经注册了要用服务运行初始化的资源. 我们需要对应用程序中可能拥有的所有资源这样做. 这是负责的代码片段:

// Register resources
    environment.jersey().register(new PartsResource(dbi.onDemand(PartsService.class)));

Service Layer

以获得正确的异常处理和独立于数据存储引擎的能力, 我们将引入一个“中间层”服务类. 这是我们将从资源层调用的类,我们不关心底层是什么. 这就是为什么我们在资源层和DAO层之间有这个层. Here is our service class:

import java.util.List;
import java.util.Objects;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Response.Status;

import org.skife.jdbi.v2.exceptions.UnableToExecuteStatementException;
import org.skife.jdbi.v2.exceptions.UnableToObtainConnectionException;
import org.skife.jdbi.v2.sqlobject.CreateSqlObject;

import com.toptal.blog.dao.PartsDao;
import com.toptal.blog.model.Part;

public abstract class PartsService {
  private static final String PART_NOT_FOUND = "部件id %s未找到。.";
  私有静态最终字符串DATABASE_REACH_ERROR =
      "Could not reach the MySQL database. 数据库可能已关闭,或者存在网络连接问题. Details: ";
  DATABASE_CONNECTION_ERROR =
      "无法创建到MySQL数据库的连接。. 数据库配置可能不正确. Details: ";
  database_expected_error =
      "试图访问数据库时发生意外错误. Details: ";
  private static final String SUCCESS = "成功...";
  ununtited_error = "删除部件时发生意外错误.";

  @CreateSqlObject
  abstract PartsDao partsDao();

  public List getParts() {
    return partsDao().getParts();
  }

  public Part getPart(int id) {
    Part part = partsDao().getPart(id);
    if (Objects.isNull(part)) {
      throw new WebApplicationException(String.format(PART_NOT_FOUND, id), Status.NOT_FOUND);
    }
    return part;
  }

  public Part createPart(Part part) {
    partsDao().createPart(part);
    return partsDao().getPart(partsDao().lastInsertId());
  }

  public Part editPart(Part part) {
    if (Objects.isNull(partsDao().getPart(part.getId()))) {
      throw new WebApplicationException(String.format(PART_NOT_FOUND, part.getId()),
          Status.NOT_FOUND);
    }
    partsDao().editPart(part);
    return partsDao().getPart(part.getId());
  }

  public String deletePart(final int id) {
    int result = partsDao().deletePart(id);
    switch (result) {
      case 1:
        return SUCCESS;
      case 0:
        throw new WebApplicationException(String.format(PART_NOT_FOUND, id), Status.NOT_FOUND);
      default:
        抛出新的WebApplicationException(ununtited_error).INTERNAL_SERVER_ERROR);
    }
  }

  public String performHealthCheck() {
    try {
      partsDao().getParts();
    } catch (unletoobtainconnectionexception ex) {
      返回checkUnableToObtainConnectionException (ex);
    } catch (UnableToExecuteStatementException ex) {
      返回checkUnableToExecuteStatementException (ex);
    } catch (Exception ex) {
      return DATABASE_UNEXPECTED_ERROR + ex.getCause().getLocalizedMessage();
    }
    return null;
  }

  private String checkunletoobtainconnectionexception (unletoobtainconnectionexception ex) {
    if (ex.getCause() instanceof java.sql.SQLNonTransientConnectionException) {
      return DATABASE_REACH_ERROR + ex.getCause().getLocalizedMessage();
    } else if (ex.getCause() instanceof java.sql.SQLException) {
      return DATABASE_CONNECTION_ERROR + ex.getCause().getLocalizedMessage();
    } else {
      return DATABASE_UNEXPECTED_ERROR + ex.getCause().getLocalizedMessage();
    }
  }

  checkUnableToExecuteStatementException(UnableToExecuteStatementException ex) {
    if (ex.getCause() instanceof java.sql.SQLSyntaxErrorException) {
      return DATABASE_CONNECTION_ERROR + ex.getCause().getLocalizedMessage();
    } else {
      return DATABASE_UNEXPECTED_ERROR + ex.getCause().getLocalizedMessage();
    }
  }
}

它的最后一部分实际上是一个健康检查实现,我们将在后面讨论.

DAO layer, JDBI, and Mapper

Dropwizard supports JDBI and Hibernate. 它是独立的Maven模块,所以让我们首先将它添加为依赖项以及MySQL连接器:


  io.dropwizard
  dropwizard-jdbi
  ${dropwizard.version}


  mysql
  mysql-connector-java
  ${mysql.connector.version}

对于简单的CRUD服务,我个人更喜欢jdbc,因为它更简单,实现起来也快得多. 我创建了一个简单的MySQL模式,其中只有一个表,仅在我们的示例中使用. 您可以在源代码中找到模式的初始化脚本. jdbc通过使用注释(如用于读取的@SqlQuery和用于写入数据的@SqlUpdate)提供简单的查询写入. Here is our DAO interface:

import java.util.List;

import org.skife.jdbi.v2.sqlobject.Bind;
import org.skife.jdbi.v2.sqlobject.BindBean;
import org.skife.jdbi.v2.sqlobject.SqlQuery;
import org.skife.jdbi.v2.sqlobject.SqlUpdate;
import org.skife.jdbi.v2.sqlobject.customizers.RegisterMapper;

import com.toptal.blog.mapper.PartsMapper;
import com.toptal.blog.model.Part;

@RegisterMapper(PartsMapper.class)
public interface PartsDao {

  @SqlQuery("select * from parts;")
  public List getParts();

  @SqlQuery("select * from parts where id =:id")
  public Part getPart(@Bind("id") final int id);

  @SqlUpdate("插入部分(名称,代码)值(:名称,:代码)")
  void createPart(@BindBean的最终部分部分);

  @SqlUpdate("更新部件集名称= coalesce(:名称, name), code = coalesce(:code, code) where id = :id")
  void editPart(@BindBean final Part part);

  @SqlUpdate("删除id =:id的部分")
  int deletePart(@Bind("id") final int id);

  @SqlQuery("select last_insert_id();")
  public int lastInsertId();
}

As you can see, it’s fairly simple. 然而,我们需要将SQL结果集映射到模型,这可以通过注册一个映射器类来实现. Here is our mapper class:

import java.sql.ResultSet;
import java.sql.SQLException;

import org.skife.jdbi.v2.StatementContext;
import org.skife.jdbi.v2.tweak.ResultSetMapper;

import com.toptal.blog.model.Part;

public class PartsMapper implements ResultSetMapper {
  private static final String ID = "id";
  private static final String NAME = "name";
  private static final String CODE = "code";

  public Part map(int i, ResultSet, ResultSet, StatementContext)
      throws SQLException {
    return new Part(resultSet.getInt(ID), resultSet.getString(NAME), resultSet.getString(CODE));
  }
}

And our model:

import org.hibernate.validator.constraints.NotEmpty;

public class Part {
  private int id;
  @NotEmpty
  private String name;
  @NotEmpty
  private String code;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getCode() {
    return code;
  }

  public void setCode(String code) {
    this.code = code;
  }

  public Part() {
    super();
  }

  public Part(int id, String name, String code) {
    super();
    this.id = id;
    this.name = name;
    this.code = code;
  }
}

Dropwizard Health Check

Dropwizard为运行状况检查提供原生支持. In our case, 在说我们的服务运行正常之前,我们可能希望检查数据库是否已启动并运行. 我们所做的实际上是执行一些简单的DB操作,例如从DB获取部件并处理潜在的结果(成功或异常)。.

下面是我们在Dropwizard中的健康检查实现:

import com.codahale.metrics.health.HealthCheck;
import com.toptal.blog.service.PartsService;

DropwizardBlogApplicationHealthCheck扩展HealthCheck {
  private static final String HEALTHY = " Dropwizard博客服务可以正常读写";
  private static final String不健康= " Dropwizard博客服务不健康. ";
  private static final String MESSAGE_PLACEHOLDER = "{}";

  private final PartsService partsService;

  公共dropwizard dblogapplicationhealthcheck (PartsService PartsService) {
    this.partsService = partsService;
  }

  @Override
  public Result check() throws Exception {
    String mySqlHealthStatus = partsService.performHealthCheck();

    if (mySqlHealthStatus == null) {
      return Result.healthy(HEALTHY);
    } else {
      return Result.不健康(不健康+ MESSAGE_PLACEHOLDER, mySqlHealthStatus);
    }
  }
}

Adding Authentication

Dropwizard支持基本身份验证和 OAuth. Here. 我将向您展示如何使用OAuth保护您的服务. However, due to complexity, 我省略了底层的DB结构,只展示了它是如何包装的. 从这里开始,全面实施应该不是问题. Dropwizard有两个我们需要实现的重要接口.

The first one is Authenticator. Our class should implement the authenticate 方法,该方法应检查给定的访问令牌是否有效. 所以我把它称为应用程序的第一扇门. If succeeded, it should return a principal. 这个主体是我们的实际用户及其角色. 这个角色对于我们需要实现的另一个Dropwizard接口很重要. This one is Authorizer, 它负责检查用户是否有足够的权限来访问某个资源. So, 如果你回去查一下我们的资源类, 您将看到它需要管理员角色来访问它的端点. These annotations can be per method also. Dropwizard授权支持是一个单独的Maven模块,所以我们需要将它添加到依赖项中:


  io.dropwizard
  dropwizard-auth
  ${dropwizard.version}

下面是我们示例中的类,它们实际上没有做任何聪明的事情, 但这是全面OAuth授权的框架:

import java.util.Optional;

import io.dropwizard.auth.AuthenticationException;
import io.dropwizard.auth.Authenticator;

public class DropwizardBlogAuthenticator implements Authenticator {
  @Override
  public Optional authenticate(String token) throws AuthenticationException {
    if ("test_token".equals(token)) {
      return Optional.of(new User());
    }
    return Optional.empty();
  }
}
import java.util.Objects;

import io.dropwizard.auth.Authorizer;

public class DropwizardBlogAuthorizer implements Authorizer {
  @Override
  公共布尔授权(用户主体,字符串角色){
    // Allow any logged in user.
    if (Objects.nonNull(principal)) {
      return true;
    }
    return false;
  }
}
import java.security.Principal;

public class User implements Principal {
  private int id;
  private String username;
  private String password;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getUsername() {
    return username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getPassword() {
    return password;
  }

  public void setPassword(String password) {
    this.password = password;
  }

  @Override
  public String getName() {
    return username;
  }
}

Unit Tests in Dropwizard

让我们在应用程序中添加一些单元测试. 我将坚持测试Dropwizard代码的特定部分, in our case Representation and Resource. 我们需要将以下依赖项添加到Maven文件中:


  io.dropwizard
  dropwizard-testing
  ${dropwizard.version}


  org.mockito
  mockito-core
  ${mockito.version}
  test

为了测试表示,我们还需要一个示例JSON文件进行测试. So let’s create fixtures/part.json under src/test/resources:

{
  "id": 1,
  "name": "testPartName",
  "code": "testPartCode"
}

And here is the JUnit test class:

import static io.dropwizard.testing.FixtureHelpers.fixture;
import static org.assertj.core.api.Assertions.assertThat;

import org.junit.Test;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.toptal.blog.model.Part;

import io.dropwizard.jackson.Jackson;

public class RepresentationTest {
  ObjectMapper MAPPER = Jackson.newObjectMapper();
  PART_JSON = "fixture /part . json ".json";
  private static final String TEST_PART_NAME = "testPartName";
  private static final String TEST_PART_CODE = "testPartCode";

  @Test
  public void serializesToJSON()抛出异常{
    最终部分部分=新部分(1,TEST_PART_NAME, TEST_PART_CODE);

    final String expected =
        MAPPER.writeValueAsString(MAPPER.readValue(fixture(PART_JSON), Part.class));

    assertThat(MAPPER.writeValueAsString(part)).isEqualTo(expected);
  }

  @Test
  公共void deserializesFromJSON()抛出异常{
    最终部分部分=新部分(1,TEST_PART_NAME, TEST_PART_CODE);

    assertThat(MAPPER.readValue(fixture(PART_JSON), Part.class).getId()).isEqualTo(part.getId());
    assertThat(MAPPER.readValue(fixture(PART_JSON), Part.class).getName())
        .isEqualTo(part.getName());
    assertThat(MAPPER.readValue(fixture(PART_JSON), Part.class).getCode())
        .isEqualTo(part.getCode());
  }
}

When it comes to testing resources, 测试Dropwizard的主要要点是,您实际上是作为HTTP客户端进行操作的, sending HTTP requests against resources. 所以,你不是像通常情况下那样测试方法. Here is the example for our PartsResource class:

public class PartsResourceTest {
  private static final String SUCCESS = "成功...";
  private static final String TEST_PART_NAME = "testPartName";
  private static final String TEST_PART_CODE = "testPartCode";
  private static final String PARTS_ENDPOINT = "/parts";

  PartsService = mock(PartsService . net.class);

  @ClassRule
  ResourceTestRule资源=
      ResourceTestRule.builder().addResource(新PartsResource (partsService)).build();

  private final Part Part = new Part(1, TEST_PART_NAME, TEST_PART_CODE);

  @Before
  public void setup() {
    when(partsService.getPart(eq(1))).thenReturn(part);
    List parts = new ArrayList<>();
    parts.add(part);
    when(partsService.getParts()).thenReturn(parts);
    when(partsService.createPart(any(Part.class))).thenReturn(part);
    when(partsService.editPart(any(Part.class))).thenReturn(part);
    when(partsService.deletePart(eq(1))).thenReturn(SUCCESS);
  }

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

  @Test
  public void testGetPart() {
    Part partResponse = resources.target(PARTS_ENDPOINT + "/1").request()
        .get(TestPartRepresentation.class).getData();
    assertThat(partResponse.getId()).isEqualTo(part.getId());
    assertThat(partResponse.getName()).isEqualTo(part.getName());
    assertThat(partResponse.getCode()).isEqualTo(part.getCode());
    verify(partsService).getPart(1);
  }

  @Test
  public void testGetParts() {
    List parts =
        resources.target(PARTS_ENDPOINT).request().get(TestPartsRepresentation.class).getData();
    assertThat(parts.size()).isEqualTo(1);
    assertThat(parts.get(0).getId()).isEqualTo(part.getId());
    assertThat(parts.get(0).getName()).isEqualTo(part.getName());
    assertThat(parts.get(0).getCode()).isEqualTo(part.getCode());
    verify(partsService).getParts();
  }

  @Test
  public void testCreatePart() {
    Part newPart = resources.target(PARTS_ENDPOINT).request()
        .post(Entity.entity(part, MediaType.APPLICATION_JSON_TYPE), TestPartRepresentation.class)
        .getData();
    assertNotNull(newPart);
    assertThat(newPart.getId()).isEqualTo(part.getId());
    assertThat(newPart.getName()).isEqualTo(part.getName());
    assertThat(newPart.getCode()).isEqualTo(part.getCode());
    verify(partsService).createPart(any(Part.class));
  }

  @Test
  public void testEditPart() {
    Part editedPart = resources.target(PARTS_ENDPOINT + "/1").request()
        .put(Entity.entity(part, MediaType.APPLICATION_JSON_TYPE), TestPartRepresentation.class)
        .getData();
    assertNotNull(editedPart);
    assertThat(editedPart.getId()).isEqualTo(part.getId());
    assertThat(editedPart.getName()).isEqualTo(part.getName());
    assertThat(editedPart.getCode()).isEqualTo(part.getCode());
    verify(partsService).editPart(any(Part.class));
  }

  @Test
  public void testDeletePart() {
    assertThat(resources.target(PARTS_ENDPOINT + "/1").request()
        .delete(TestDeleteRepresentation.class).getData()).isEqualTo(SUCCESS);
    verify(partsService).deletePart(1);
  }

  private static class TestPartRepresentation extends Representation {

  }

  private static class TestPartsRepresentation extends Representation> {

  }

  private static class TestDeleteRepresentation extends Representation {

  }
}

Build Your Dropwizard Application

最佳实践是构建单个FAT JAR文件,其中包含所有的 .运行应用程序所需的类文件. 可以将相同的JAR文件部署到从测试到生产的不同环境中,而无需对依赖库进行任何更改. 开始将我们的示例应用程序构建为一个胖JAR, 我们需要配置一个Maven插件Maven -shade. 您必须在您的插件部分添加以下条目.xml file.

下面是构建JAR文件的示例Maven配置.


  4.0.0
  com.endava
  dropwizard-blog
  0.0.1-SNAPSHOT
  Dropwizard Blog example

  
    1.1.0
    2.7.12
    6.0.6
    1.8
    1.8
  

  
    
      io.dropwizard
      dropwizard-core
      ${dropwizard.version}
    
    
      io.dropwizard
      dropwizard-jdbi
      ${dropwizard.version}
    
    
      io.dropwizard
      dropwizard-auth
      ${dropwizard.version}
    
    
      io.dropwizard
      dropwizard-testing
      ${dropwizard.version}
    
    
      org.mockito
      mockito-core
      ${mockito.version}
      test
    
    
      mysql
      mysql-connector-java
      ${mysql.connector.version}
    
  

  
    
      
        org.apache.maven.plugins
        maven-shade-plugin
        2.3
        
          true
          
            
              *:*
              
                META-INF/*.SF
                META-INF/*.DSA
                META-INF/*.RSA
              
            
          
        
        
          
            package
            
              shade
            
            
              
                
                
                  com.endava.blog.DropwizardBlogApplication
                
              
            
          
        
      
    
  

Running Your Application

Now, we should be able to run the service. 如果您已经成功构建了JAR文件, 你所需要做的就是打开命令提示符,然后运行下面的命令来执行JAR文件:

java -jar target/dropwizard-blog-1.0.0.jar server configuration.yml

如果一切正常,那么你会看到这样的内容:

INFO  [2017-04-23 22:51:14,471] org.eclipse.jetty.util.log: Logging initialized @962ms to org.eclipse.jetty.util.log.Slf4jLog
INFO  [2017-04-23 22:51:14,537] io.dropwizard.server.DefaultServerFactory:注册带有根路径前缀:/的球衣处理程序
INFO  [2017-04-23 22:51:14,538] io.dropwizard.server.DefaultServerFactory:注册带有根路径前缀:/的管理处理程序
INFO  [2017-04-23 22:51:14,681] io.dropwizard.server.DefaultServerFactory:注册带有根路径前缀:/的球衣处理程序
INFO  [2017-04-23 22:51:14,681] io.dropwizard.server.DefaultServerFactory:注册带有根路径前缀:/的管理处理程序
INFO  [2017-04-23 22:51:14,682] io.dropwizard.server.ServerFactory:启动dropwizard dblogapplication
INFO  [2017-04-23 22:51:14,752] org.eclipse.jetty.setuid.SetUIDListener:打开application@7d57dbb5{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO  [2017-04-23 22:51:14,752] org.eclipse.jetty.setuid.SetUIDListener:打开admin@630b6190{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
INFO  [2017-04-23 22:51:14,753] org.eclipse.jetty.server.Server: jetty-9.4.2.v20170220
INFO  [2017-04-23 22:51:15,153] io.dropwizard.jersey.dropwizardresourcecconfig:为配置的资源找到以下路径:

    GET     /parts (com.toptal.blog.resource.PartsResource)
    POST    /parts (com.toptal.blog.resource.PartsResource)
    DELETE  /parts/{id} (com.toptal.blog.resource.PartsResource)
    GET     /parts/{id} (com.toptal.blog.resource.PartsResource)
    PUT     /parts/{id} (com.toptal.blog.resource.PartsResource)

INFO  [2017-04-23 22:51:15,154] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@58fa5769 {/, null,可用}
INFO  [2017-04-23 22:51:15,158] io.dropwizard.setup.AdminEnvironment: tasks = 

    POST    /tasks/log-level (io.dropwizard.servlets.tasks.LogConfigurationTask)
    POST    /tasks/gc (io.dropwizard.servlets.tasks.GarbageCollectionTask)

INFO  [2017-04-23 22:51:15,162] org.eclipse.jetty.server.handler.ContextHandler: Started i.d.j.MutableServletContextHandler@3fdcde7a {/, null,可用}
INFO  [2017-04-23 22:51:15,176] org.eclipse.jetty.server.AbstractConnector:已启动application@7d57dbb5{HTTP/1.1,[http/1.1]}{0.0.0.0:8080}
INFO  [2017-04-23 22:51:15,177] org.eclipse.jetty.server.AbstractConnector:已启动admin@630b6190{HTTP/1.1,[http/1.1]}{0.0.0.0:8081}
INFO  [2017-04-23 22:51:15,177] org.eclipse.jetty.server.Server: Started @1670ms

现在,您已经拥有了自己的Dropwizard应用程序,该应用程序在端口8080上侦听应用程序请求,在端口8081上侦听管理请求.

Note that server configuration.yml 用于启动HTTP服务器并将YAML配置文件位置传递给服务器.

Excellent! 最后,我们使用Dropwizard框架实现了一个微服务. 现在我们休息一下,喝杯茶吧. You have done really good job.

Accessing Resources

您可以使用任何HTTP客户端,如POSTMAN或其他任何客户端. 您应该能够通过点击访问服务器 http://localhost:8080/parts. 您应该会收到一条消息,表明访问服务需要凭据. To authenticate, add Authorization header with bearer test_token value. 如果操作成功,您应该看到如下内容:

{
  "code": 200,
  "data": []
}

meaning that your DB is empty. 通过将HTTP方法从GET切换到POST来创建第一部分,并提供以下有效负载:

{
  "name":"My first part",
  "code":"code_of_my_first_part"
}

所有其他端点都以相同的方式工作,所以继续玩并享受.

How to Change Context Path

默认情况下,Dropwizard应用程序将在 /. For example, 如果您没有提及任何有关应用程序的上下文路径, by default, 可以从URL访问应用程序 http://localhost:8080/. 如果您想为您的应用程序配置自己的上下文路径, 那么请将以下条目添加到您的YAML文件中.

server:
    applicationContextPath: /application

Wrapping up our Dropwizard Tutorial

现在,当您启动并运行Dropwizard REST服务时, 让我们总结一下使用Dropwizard作为REST框架的一些主要优点或缺点. 从这篇文章中可以明显看出,Dropwizard提供了非常快的项目引导. 这可能是使用Dropwizard最大的优势.

Also, 它将包括您在开发服务时需要的所有尖端库/工具. 所以你完全不需要担心这个. 它还提供了非常好的配置管理. 当然,Dropwizard也有一些缺点. 通过使用Dropwizard,你只能使用Dropwizard提供或支持的内容. 在开发过程中,您可能会失去一些自由. But still, I wouldn’t even call it a disadvantage, 因为这正是使Dropwizard成为它的原因-易于设置, easy to develop, 而且是一个非常健壮和高性能的REST框架.

In my opinion, 通过支持越来越多的第三方库来增加框架的复杂性也会在开发中引入不必要的复杂性.

Understanding the basics

  • 具象状态转移(REST)的含义是什么??

    具象状态传输(Representational State Transfer, REST)是一种架构风格(不能与标准集混用),它基于一组描述如何定义和处理网络资源的原则.

  • What is a REST end point?

    REST端点是REST服务的公开HTTP入口点. 在上面的示例中,GET /parts是一个端点,而POST /parts是另一个端点.

  • What is a REST web service?

    REST web服务是按照REST架构风格构建的任何web应用程序. 它监听HTTP或HTTPs端口上的请求,并将其端点公开给客户端.

  • What is a microservice architecture?

    它是一个松散耦合服务的体系结构. 但是,这些服务一起实现整个系统的业务逻辑. 微服务的最大好处在于,每个小服务都更容易开发, understand, 在不影响系统其余部分的情况下进行部署或维护.

就这一主题咨询作者或专家.
Schedule a call
Dusan Simonovic's profile image
Dusan Simonovic

Located in Belgrade, Serbia

Member since February 8, 2017

About the author

Dusan是一名后端Java开发人员,拥有8年的Java开发经验,参与过许多大型项目.

Toptal作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.

Expertise

Previously At

InterVenture

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

World-class articles, delivered weekly.

Subscription implies consent to our privacy policy

Toptal Developers

Join the Toptal® community.