发布于 2026-01-06 1 阅读
0

使用 Dropwizard、MongoDB 和 Docker 构建微服务 DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 Dropwizard、MongoDB 和 Docker 构建微服务

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

Dropwizard 施展魔法。😎

在本教程中,我将使用 Dropwizard 创建一个微服务。Dropwizard 框架提供了构建 Web 应用程序所需的各种库。其中包括以下一些库:

Jetty 服务器:这是一个开源的 Web 服务器,轻量级且易于嵌入任何应用程序。

Jersey:用于创建 RESTful Web 服务的实现。

Jackson:允许您在 JSON 对象和 POJO 之间进行操作,反之亦然。

指标:提供有关 HTTP 请求、数据库连接、队列等状态的信息。

Guava:提供多种类来处理集合、验证、字符串等。

Hibernate Validator:将验证作为 Java 对象的约束。

JDBI:封装了 JDBC,并提供了一种灵活的方式来操作关系数据库。

Liquibase:非常适合数据库迁移和 DDL 更改。

MongoDB 是一款 NoSQL 数据库,功能远不止于此。🌳

这种面向文档(以 JSON 等文档形式)的 NoSQL 数据库结合了关系数据库的一些特性,易于使用,并且是多平台的,是扩展、容错、负载均衡、MapReduce 等的最佳选择。

此微服务中使用的技术📝

  • OpenJDK 8
  • Docker 2.0.0.0-mac81(Mac 版本)
  • MongoDB 4.0
  • Maven 3.5
  • Mac OS Mojave(或其他类似 Windows 或 Linux 的系统)
  • Nginx 1.15
  • IntelliJ IDEA 2018

好的,我们开始创建项目。Dropwizard 提供了一个 Maven 原型,我们可以从其网站上使用,以下是该命令的示例:

mvn archetype:generate -DarchetypeGroupId=io.dropwizard.archetypes -DarchetypeArtifactId=java-simple -DarchetypeVersion=1.3.5 -DgroupId=com.demo -DartifactId=dropwizard-mongodb-ms -Dversion=1.0.0-SNAPSHOT -Dname=DropwizardMongoDBMicroservice
Enter fullscreen mode Exit fullscreen mode

打开终端,在命令前粘贴内容,确保已安装并配置 Maven 和 Java。

最终结构如下:

dropwizard-mongodb-ms/
├── README.md
├── config.yml
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── demo
    │   │           ├── DropwizardMongoDBMicroserviceApplication.java
    │   │           ├── DropwizardMongoDBMicroserviceConfiguration.java
    │   │           ├── api
    │   │           ├── cli
    │   │           ├── client
    │   │           ├── core
    │   │           ├── db
    │   │           ├── health
    │   │           └── resources
    │   └── resources
    │       ├── assets
    │       └── banner.txt
    └── test
        ├── java
        │   └── com
        │       └── demo
        │           ├── api
        │           ├── client
        │           ├── core
        │           ├── db
        │           └── resources
        └── resources
            └── fixtures
Enter fullscreen mode Exit fullscreen mode

项目创建完成后,在pom.xml文件中添加依赖项:

. . .
<properties>
        <dropwizard.version>1.3.5</dropwizard.version>
        <mainClass>com.demo.DropwizardMongoDBMicroserviceApplication</mainClass>
        <mongodb.version>3.8.2</mongodb.version>
        <jdk.version>1.8</jdk.version>
        <dropwizard.swagger.version>1.0.6-1</dropwizard.swagger.version>
        <mockito.core.version>2.23.0</mockito.core.version>
</properties>
. . .
<dependency>
    <groupId>io.dropwizard</groupId>
    <artifactId>dropwizard-core</artifactId>
</dependency>
<dependency>
    <groupId>org.mongodb</groupId>
    <artifactId>mongodb-driver-sync</artifactId>
    <version>${mongodb.version}</version>
</dependency>

<dependency>
    <groupId>com.smoketurner</groupId>
    <artifactId>dropwizard-swagger</artifactId>
    <version>${dropwizard.swagger.version}</version>
</dependency>

<!-- Testing -->
<dependency>
    <groupId>io.dropwizard</groupId>
    <artifactId>dropwizard-testing</artifactId>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>${mockito.core.version}</version>
    <scope>test</scope>
</dependency>
<!-- Testing -->
. . .
Enter fullscreen mode Exit fullscreen mode

我在文件中添加了一些配置configuration.yaml,如下所示:

server:
  maxThreads: 512
  applicationContextPath: /dropwizard-mongodb-ms
  applicationConnectors:
    - type: http
      port: 8080
  adminConnectors:
    - type: http
      port: 8081

logging:
  level: INFO
  loggers:
    com.demo: INFO

#You can choose the user and password what you want.
mongoDBConnection:
  credentials:
    username: "user_donuts" 
    password: "pAsw0Rd"
  seeds:
    - host: "mongodb"
      port: 27017
  database: "donuts"

swagger:
  basePath: /dropwizard-mongodb-ms
  resourcePackage: com.demo.resources
  scan: true
  info:
    version: "1.0.0"
    title: "Donuts API CRUD"
    description: "A simple API used for expose CRUD operation on MongoDB collection"
    termsOfService: "http://swagger.io/terms/"
    contact:
      name: "Donuts API "
    license:
      name: "Rich"
Enter fullscreen mode Exit fullscreen mode

configuration.yml 文件映射到一个继承自某个类的类,io.dropwizard.Configuration其内容如下所示:

package com.demo;

import com.demo.db.configuration.MongoDBConnection;
import com.fasterxml.jackson.annotation.JsonProperty;

import io.dropwizard.Configuration;
import io.federecio.dropwizard.swagger.SwaggerBundleConfiguration;

public class DropwizardMongoDBMicroserviceConfiguration extends Configuration {

    /**
     * The data configuration for MongoDB.
     */

    private MongoDBConnection mongoDBConnection;

    @JsonProperty("swagger")
    private SwaggerBundleConfiguration swaggerBundleConfiguration;

    //Getters and setters
}
Enter fullscreen mode Exit fullscreen mode

这个微服务将基于 CRUD(创建、读取、更新、删除)公开一个 REST API。我选择了一个类似甜甜圈的产品(🍩 🤤),你可以在本教程末尾的我的 Github 存储库中找到部分代码。

我使用com.mongodb.client.MongoCollection名为“用于访问集合的接口”donuts来更精细地控制操作,因此我创建了一个 DAO(数据访问对象)来操作 CRUD 操作。

package com.demo.db.daos;
//imports ... 
public class DonutDAO {

    /** The collection of Donuts */
    final MongoCollection<Document> donutCollection;

    /**
     * Constructor.
     *
     * @param donutCollection the collection of donuts.
     */
    public DonutDAO(final MongoCollection<Document> donutCollection) {
        this.donutCollection = donutCollection;
    }

    /**
     * Find all donuts.
     *
     * @return the donuts.
     */
    public List<Donut> getAll() {
        final MongoCursor<Document> donuts = donutCollection.find().iterator();
        final List<Donut> donutsFind = new ArrayList<>();
        try {
            while (donuts.hasNext()) {
                final Document donut = donuts.next();
                donutsFind.add(DonutMapper.map(donut));
            }
        } finally {
            donuts.close();
        }
        return donutsFind;
    }

    /**
     * Get one document find in other case return null.
     *
     * @param id the identifier for find.
     * @return the Donut find.
     */
    public Donut getOne(final ObjectId id) {
        final Optional<Document> donutFind = Optional.ofNullable(donutCollection.find(new Document("_id", id)).first());
        return donutFind.isPresent() ? DonutMapper.map(donutFind.get()) : null;
    }

    public void save(final Donut donut){
        final Document saveDonut = new Document("price", donut.getPrice())
                                      .append("flavor", donut.getFlavor());
        donutCollection.insertOne(saveDonut);
    }

    /**
     * Update a register.
     *
     * @param id the identifier.
     * @param donut the object to update.
     */
    public void update(final ObjectId id, final Donut donut) {
        donutCollection.updateOne(new Document("_id", id),
                new Document("$set", new Document("price", donut.getPrice())
                        .append("flavor", donut.getFlavor()))
        );
    }

    /**
     * Delete a register.
     * @param id    the identifier.
     */
    public void delete(final ObjectId id){
        donutCollection.deleteOne(new Document("_id", id));
    }
Enter fullscreen mode Exit fullscreen mode

为了映射来自数据库的信息,我使用了一个实用程序类,该类将 MongoDB 中的字段过滤到 POJO 中。

package com.demo.util;

import com.demo.api.Donut;
import org.bson.Document;

public class DonutMapper {

    /**
     * Map objects {@link Document} to {@link Donut}.
     *
     * @param donutDocument the information document.
     * @return A object {@link Donut}.
     */
    public static Donut map(final Document donutDocument) {
        final Donut donut = new Donut();
        donut.setId(donutDocument.getObjectId("_id"));
        donut.setFlavor(donutDocument.getString("flavor"));
        donut.setPrice(donutDocument.getDouble("price"));
        return donut;
    }
}
Enter fullscreen mode Exit fullscreen mode

然后创建一个 POJO 来操作 MongoDB 中的信息。

package com.demo.api;
//imports ...
public class Donut implements Serializable {

    /** The id.*/
    @JsonSerialize(using = ObjectIdSerializer.class)
    private ObjectId id;

    /** The price. */
    @NotNull
    private double price;

    /** The principal flavor.*/
    @NotNull
    private String flavor;

    /**Constructor.*/
    public Donut() {
    }

//getters & setters
//hashcode, equals and toString methods
Enter fullscreen mode Exit fullscreen mode

此类仅用于响应端点。

package com.demo.api;
//imports ...
public class Response {
    /** The message.*/
    private String message;

    /** Constructor.*/
    public Response() {
    }
//getters & setters
//hashcode, equals and toString methods
Enter fullscreen mode Exit fullscreen mode

这里还有更多使用 Jackson转换org.bson.types.ObjectId为对象的实用类。String

package com.demo.util;

import java.io.IOException;

import org.bson.types.ObjectId;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

public class ObjectIdSerializer extends JsonSerializer<ObjectId> {
    @Override
    public void serialize(final ObjectId objectId, final JsonGenerator jsonGenerator,
                          final SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(objectId.toString());
    }
}
Enter fullscreen mode Exit fullscreen mode

String对象转换为数组char

package com.demo.util;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;

import java.io.IOException;

public class PasswordSerializer extends JsonSerializer<String> {
    @Override
    public void serialize(final String input, final JsonGenerator jsonGenerator,
                          final SerializerProvider serializerProvider) throws IOException {
        jsonGenerator.writeString(input.toCharArray(), 0, input.toCharArray().length);
    }

    @Override
    public Class<String> handledType() {
        return String.class;
    }
}
Enter fullscreen mode Exit fullscreen mode

使用这些 POJO 将信息映射到configuration.yml

package com.demo.db.configuration;

import java.util.Arrays;
import java.util.Objects;

import com.demo.util.PasswordSerializer;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

/**
 * This class is used for credentials.
 * @version 1.0.0
 * @since 1.0.0
 * @author Rich Lopez
 */
public class Credentials {

    /** The user name.*/
    private String username;

    /** The password.*/
    @JsonSerialize(using = PasswordSerializer.class)
    private char[] password;

    //getters, setters, hashcode and equals methods.
}
Enter fullscreen mode Exit fullscreen mode
package com.demo.db.configuration;

import java.util.Objects;

public class Seed {

    /** The host.*/
    private String host;

    /** The port.*/
    private int port;
    //getters, setters, hashcode and equals methods.
}
Enter fullscreen mode Exit fullscreen mode
package com.demo.db.configuration;

import java.util.List;

public class MongoDBConnection {

    /**
     * The credentials user and password.
     */
    private Credentials credentials;

    /**
     * The lis of seeds.
     */
    private List<Seed> seeds;

    /**
     * The db.
     */
    private String database;
}
Enter fullscreen mode Exit fullscreen mode

为了管理与 MongoDB 的连接,我们需要将来自 yaml 文件配置的信息映射到这些类com.demo.db.configuration.Credentials,,com.demo.db.configuration.MongoDBConnectioncom.demo.db.configuration.Seed

该类com.demo.db.MongoDBFactoryConnection使用 YAML 文件中提供的选项创建 MongoDB 客户端,创建实例的方法如下所示:

package com.demo.db;
//imports ...
public class MongoDBFactoryConnection {

    /** The configuration for connect to MongoDB Server.*/
    private MongoDBConnection mongoDBConnection;

    /**
     * Constructor.
     *
     * @param mongoDBConnection the mongoDB connection data.
     */
    public MongoDBFactoryConnection(final MongoDBConnection mongoDBConnection) {
        this.mongoDBConnection = mongoDBConnection;
    }

    /**
     * Gets the connection to MongoDB.
     *
     * @return the mongo Client.
     */
    public MongoClient getClient() {
        LOGGER.info("Creating mongoDB client.");
        final Credentials configCredentials = mongoDBConnection.getCredentials();

        final MongoCredential credentials = MongoCredential.createCredential(
                configCredentials.getUsername(),
                mongoDBConnection.getDatabase(),
                configCredentials.getPassword());

        final MongoClient client = MongoClients.create(
                MongoClientSettings.builder()
                        .credential(credentials)
                        .applyToClusterSettings(builder -> builder.hosts(getServers())).build()
        );

        return client;
    }

    /**
     * Map the object {@link Seed} to objects {@link ServerAddress} that contain the information of servers.
     *
     * @return the list of servers.
     */
    private List<ServerAddress> getServers() {
        final List<Seed> seeds = mongoDBConnection.getSeeds();
        return seeds.stream()
                .map(seed -> {
                    final ServerAddress serverAddress = new ServerAddress(seed.getHost(), seed.getPort());
                    return serverAddress;
                })
                .collect(Collectors.toList());
    }
Enter fullscreen mode Exit fullscreen mode

在 Dropwizard 生命周期中,对象可以通过接口io.dropwizard.lifecycle.Managed和类进行管理io.dropwizard.lifecycle.MongoDBManaged

package com.demo.db;
//imports ...
public class MongoDBManaged implements Managed {

    /** The mongoDB client.*/
    private MongoClient mongoClient;

    /**
     * Constructor.
     * @param mongoClient   the mongoDB client.
     */
    public MongoDBManaged(final MongoClient mongoClient) {
        this.mongoClient = mongoClient;
    }

    @Override
    public void start() throws Exception {
    }

    @Override
    public void stop() throws Exception {
        mongoClient.close();
    }
Enter fullscreen mode Exit fullscreen mode

另一个重要的类是 Healthcheck,创建此类只需要继承com.codahale.metrics.health.HealthCheck并实现该方法即可check

package com.demo.health;
//imports ...
public class DropwizardMongoDBHealthCheck extends HealthCheck {
     /** A client of MongoDB.*/
    private MongoClient mongoClient;

    /**
     * Constructor.
     *
     * @param mongoClient the mongo client.
     */
    public DropwizardMongoDBHealthCheck(final MongoClient mongoClient) {
        this.mongoClient = mongoClient;
    }
    @Override
    protected Result check() {
        try {
            final Document document = mongoClient.getDatabase("donuts").runCommand(new Document("buildInfo", 1));
            if (document == null) {
                return Result.unhealthy("Can not perform operation buildInfo in Database.");
            }
        } catch (final Exception e) {
            return Result.unhealthy("Can not get the information from database.");
        }
        return Result.healthy();
    }
Enter fullscreen mode Exit fullscreen mode

该微服务的入口点是加载com.demo.DropwizardMongoDBMicroserviceApplication所有捆绑包配置、初始化类等的类。此类必须继承自某个类,io.dropwizard.Application并且由该类的一个子类进行参数化io.dropwizard.Configuration

package com.demo;

import com.demo.db.MongoDBFactoryConnection;
import com.demo.db.daos.DonutDAO;
import com.demo.db.MongoDBManaged;
import com.demo.health.DropwizardMongoDBHealthCheck;
import com.demo.resources.DonutResource;

import io.dropwizard.Application;
import io.dropwizard.setup.Bootstrap;
import io.dropwizard.setup.Environment;
import io.federecio.dropwizard.swagger.SwaggerBundle;
import io.federecio.dropwizard.swagger.SwaggerBundleConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class DropwizardMongoDBMicroserviceApplication extends Application<DropwizardMongoDBMicroserviceConfiguration> {

    /**
     * Logger class.
     */
    private static final Logger LOGGER = LoggerFactory.getLogger(DropwizardMongoDBMicroserviceApplication.class);

    /**
     * Entry point for start Application.
     *
     * @param args the args.
     * @throws Exception when the app can not start.
     */
    public static void main(final String[] args) throws Exception {
        LOGGER.info("Start application.");
        new DropwizardMongoDBMicroserviceApplication().run(args);
    }

    @Override
    public String getName() {
        return "DropwizardMongoDBMicroservice";
    }

    @Override
    public void initialize(final Bootstrap<DropwizardMongoDBMicroserviceConfiguration> bootstrap) {
        bootstrap.addBundle(new SwaggerBundle<DropwizardMongoDBMicroserviceConfiguration>() {
            @Override
            protected SwaggerBundleConfiguration getSwaggerBundleConfiguration(
                        final DropwizardMongoDBMicroserviceConfiguration dropwizardMongoDBMicroserviceConfiguration) {
                return dropwizardMongoDBMicroserviceConfiguration.getSwaggerBundleConfiguration();
            }
        });
    }

    @Override
    public void run(final DropwizardMongoDBMicroserviceConfiguration configuration,
                    final Environment environment) {

        final MongoDBFactoryConnection mongoDBManagerConn = new MongoDBFactoryConnection(configuration.getMongoDBConnection());

        final MongoDBManaged mongoDBManaged = new MongoDBManaged(mongoDBManagerConn.getClient());

        final DonutDAO donutDAO = new DonutDAO(mongoDBManagerConn.getClient()
                .getDatabase(configuration.getMongoDBConnection().getDatabase())
                .getCollection("donuts"));

        environment.lifecycle().manage(mongoDBManaged);
        environment.jersey().register(new DonutResource(donutDAO));
        environment.healthChecks().register("DropwizardMongoDBHealthCheck",
                new DropwizardMongoDBHealthCheck(mongoDBManagerConn.getClient()));
    }
}
Enter fullscreen mode Exit fullscreen mode

请使用以下命令构建项目:

$ mvn clean package

在启动应用程序之前,需要有数据库,对于 MongoDB,您可以从网站下载,或者使用 DockerHub 中的 docker 镜像。

下载完 MongoDB 镜像后,启动一个新的容器:

$ docker run --name mongodb -d -p 27017:27017 mongo

注意:务必创建卷以保持信息的持久性。

进入容器并输入以下命令:

$ docker exec -it [container name or id] /bin/bash
$ mongo
$ use donuts
> db.createUser({ user: "user_donuts", pwd: "pAsw0Rd", roles: [ { role: "readWrite", db: "donuts"} ]});

默认情况下,MongoDB 将身份验证机制设置为 SCRAM。

现在使用以下命令启动应用程序:

$ java -jar target/dropwizard-mongodb-ms-1.0.0-SNAPSHOT.jar server configuration.yml

创建完用户和数据库后,我们就可以在以下 URL 中输入 Swagger 文档:

http://localhost:8080/dropwizard-mongodb-ms/swagger

Swagger 文档

使用 Docker Compose 🐳

我们每天使用 Docker 的原因之一是它提供了一个统一的环境,所有开发人员都能保持依赖项、数据库、Web 服务器等的同步。这种简便的构建和部署方式缩短了交付时间。

我使用 Docker 启动应用程序和 MongoDB 服务器,此外,我还添加了Nginx 代理来接收请求。以下是一个 Docker Compose 文件示例:

version: '3'
services:
  mongodb:
    image: mongo
    restart: always
    container_name: mongodb
    environment:
      MONGO_INITDB_ROOT_USERNAME: admin
      MONGO_INITDB_ROOT_PASSWORD: admin
    ports:
      - 27017:27017
    networks:
      - dropw-mongodb-ntw

  nginx:
    image: nginx
    container_name: nginx
    volumes:
    - ./nginx.conf:/etc/nginx/nginx.conf
    ports:
    - "8080:80"
    - "443:443"
    networks:
    - dropw-mongodb-ntw

  dropw-ms:
    image: openjdk:8-jre
    container_name: dropw-ms
    volumes:
    - ./target/dropwizard-mongodb-ms-1.0.0-SNAPSHOT.jar:/microservice/dropwizard-mongodb-ms-1.0.0-SNAPSHOT.jar
    - ./configuration.yml:/microservice/configuration.yml
    working_dir: /microservice
    command: ["java", "-jar", "dropwizard-mongodb-ms-1.0.0-SNAPSHOT.jar", "server", "configuration.yml"]
    ports:
    - "8090:8080"
    - "8091:8081"
    networks:
    - dropw-mongodb-ntw

networks:
  dropw-mongodb-ntw:
    external:
      name: dropw-mongodb-ntw
Enter fullscreen mode Exit fullscreen mode

我们使用以下命令创建 Docker 网络:

docker network create dropw-mongodb-ntw

使用以下命令构建项目:

$ mvn clean package

启动 Docker Compose:

$ docker-compose up

创建完创建数据库用户所需的所有服务后,我们将使用以下命令访问容器。

$ docker exec -it [container name or id] /bin/bash
$ mongo
> use donuts
> db.createUser({ user: "user_donuts", pwd: "pAsw0Rd", roles: [ { role: "readWrite", db: "donuts"} ]});

http://localhost:8080/dropwizard-mongodb-ms/swagger

结论

Dropwizard 为我们提供了多种工具,可以轻松创建微服务。例如,您可以将其与其他技术(如 MongoDB、Docker 和 Nginx)集成。请记住,一切都取决于具体需求和待解决的问题,但对我来说,它非常适合入门和分享经验。

GitHub 仓库:https://github.com/ricdev2/dropwizard-mongodb-ms

文章来源:https://dev.to/ricdev2/building-microservices-with-dropwizard-mongodb--docker--o30