使用 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
打开终端,在命令前粘贴内容,确保已安装并配置 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
项目创建完成后,在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 -->
. . .
我在文件中添加了一些配置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"
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
}
这个微服务将基于 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));
}
为了映射来自数据库的信息,我使用了一个实用程序类,该类将 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;
}
}
然后创建一个 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
此类仅用于响应端点。
package com.demo.api;
//imports ...
public class Response {
/** The message.*/
private String message;
/** Constructor.*/
public Response() {
}
//getters & setters
//hashcode, equals and toString methods
这里还有更多使用 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());
}
}
将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;
}
}
使用这些 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.
}
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.
}
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;
}
为了管理与 MongoDB 的连接,我们需要将来自 yaml 文件配置的信息映射到这些类com.demo.db.configuration.Credentials,,com.demo.db.configuration.MongoDBConnection。com.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());
}
在 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();
}
另一个重要的类是 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();
}
该微服务的入口点是加载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()));
}
}
请使用以下命令构建项目:
$ 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
使用 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
我们使用以下命令创建 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
