在微服务天堂中 Ratpack 和 Spring Boot 是天造地设的一对。它们都是以开发者为中心的运行于 JVM 之上的 web 框架,侧重于生产率、效率以及轻量级部署。他们在服务程序的开发中带来了各自的好处。Ratpack 通过一个高吞吐量、非阻塞式的 web 层提供了一个反应式编程模型,而且对应用程序结构的定义和 HTTP 请求过程提供了一个便利的处理程序链;Spring Boot 集成了整个 Spring 生态系统,为应用程序提供了一种简单的方式来配置和启用组件。Ratpack 和 Spring Boot 是构建原生支持计算云的基于数据驱动的微服务的不二选择。
Ratpack 并不关心应用程序底层使用了什么样的依赖注入框架。相反,应用程序可以通过 Ratpack 提供的 DI 抽象(被称为 Registry)访问服务层组件。Ratpack 的 Registry 是构成其基础设施的一部分,其提供了一个接口,DI 提供者可以使用注册器回调(registry backing)机制来参与到组件解决方案序列中。
Ratpack 直接为 Guice 和 Spring Boot 提供了注册器回调机制,开发人员可以为应用程序灵活选择使用的依赖注入框架。
在本文中我们将演示使用 Ratpack 和 Spring Boot 构建一个 RESTful 风格的基于数据驱动的微服务,背后使用了 Spring Data 用于操作数据。
开始构建 Ratpack 项目的最佳方式是创建 Gradle 脚本以及标准的 Java 项目结构。Gradle 是 Ratpack 原生支持的构建系统,其实由于 Ratpack 只是一组简单的 JVM 库,所以其实它适用于任何构建系统(不管你的需求有多特别)。如果你还未安装 Gradle,那么安装它最佳方式是通过 Groovy enVironment Manager 工具。示例项目的构建脚本如列表 1 所示。
列表 1
buildscript { repositories { jcenter() } dependencies { classpath <span>'io.ratpack:ratpack-gradle:0.9.18'</span> } } apply plugin: <span>'io.ratpack.ratpack-java'</span> apply plugin: <span>'idea'</span> apply plugin: <span>'eclipse'</span> repositories { jcenter() } dependencies { compile ratpack.dependency(<span>'spring-boot'</span>) (<span>1</span>) } mainClassName = <span>"springpack.Main"</span> (<span>2</span>) eclipse { classpath { containers.remove(<span>'org.eclipse.jdt.launching.JRE_CONTAINER'</span>) containers <span>'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'</span> } }
在 (1) 部分中,构建脚本通过调用 Ratpack Gradle 插件的 ratpack.dependency(…) 方法引入了 Ratpack 和 Spring Boot 的集成。根据构建脚本和当前项目结构,我们可以创建一个“主类”(main class),其作为可运行的类来启动和运行应用程序。注意(2)中我们指定了主类的名称,所以使用命令行工具时会更简练。这意味着实际的主类名必须与之一致,所以需要在本项目的 src/main/java 目录中创建一个名为 springpack.Main 的类。
在主类中,我们通过工厂方法构造了 RatpackServer 的一个实例,在 start 方法中提供了对应用程序的定义。该定义中我们编写了 RESTful API 处理器链。请参见列表 2 中对 Main 类的演示。注意 Ratpack 要求的编译环境为 Java 8。
列表 2
<span>package</span> springpack; <span>import</span> ratpack.server.RatpackServer; <span>public</span> <span><span>class</span> <span>Main</span> {</span> <span>public</span> <span>static</span> <span>void</span> <span>main</span>(String[] args) <span>throws</span> Exception { RatpackServer.start(spec -> spec .handlers(chain -> chain (<span>1</span>) .prefix(<span>"api"</span>, pchain -> pchain (<span>2</span>) .all(ctx -> ctx (<span>3</span>) .byMethod(method -> method (<span>4</span>) .get(() -> ctx.render(<span>"Received GET request"</span>)) .post(() -> ctx.render(<span>"Received POST request"</span>)) .put(() -> ctx.render(<span>"Received PUT request"</span>)) .delete(() -> ctx.render(<span>"Received DELETE request"</span>)) ) ) ) ) ); } }
如果我们仔细剖析主类中的应用程序定义,我们可以识别出一些关键知识点,对于不熟悉 Ratpack 的人来说,我们需要对这些知识点做进一步解释。第一个值得注意的点在(1)中处理器区域定义了一个处理器链,该处理器链用于处理 Ratpack 流中的 HTTP 请求。通过链式定义的处理器描述了它们能够处理的请求类型。特别在(2)中我们定义了一个前缀处理器类型,指定它被绑定到“api”这个 HTTP 路由。前缀处理器创建了一个新的处理器链,用来处理匹配”/api” 端口 (endpoint) 到来的请求。在(3)处我们使用了所有的处理器类型来指定所有到来的请求应该运行在我们提供的处理器中,在(4)处我们使用 Ratpack 的 byMethod 机制来将 get,post,put 和 delete 处理器绑定到到各自的 HTTP 方法中。
在项目根目录下,我们可以通过命令行简单使用 gradle 的“run”命令运行该应用程序。这会启动 web 服务器并绑定到端口 5050。为了演示当前项目的功能,确保处理器结构工作正常,我们可以在命令行中通过 curl 运行一些测试:
- 命令:curl http://localhost:5050 ,期待输出:Received GET request
- 命令:curl -XPOST http://localhost:5050 , 期待输出:Received POST request
- 命令:curl -XPUT http://localhost:5050 , 期待输出:Received PUT request
- 命令:curl -XDELETE http://localhost:5050 , 期待输出:Received DELETE request
可以看到,应用程序处理器链可以正确地路由请求,我们建立了 RESTful API 的结构。接下来需要改善这些 API…
为了演示的缘故,让我们尽量保持简单,改造该微服务以便可以对一个 User 领域对象进行 CRUD 操作。通过 REST 接口,客户可以做以下事情:
- 通过一个 GET 请求来请求指定的用户账号,用户名作为路径变量(path variable);
- GET 请求中如果未指定用户名,则列出所有的用户;
- 通过 POST 一个 JSON 格式的用户对象来创建一个用户;
- 使用 PUT 请求,用户名作为路径变量来更新该用户的邮件地址;
- 使用 DELETE 请求,用户名作为路径变量来删除该用户。
在之前小节中我们定义的处理器已经包含了大多数处理这种需求的基础设施。但根据需求我们还需要做细微调整。例如,我们现在需要绑定处理器接收用户名作为路径变量。列表 3 中是更新后的代码,主类中的处理器可以满足现在的需求。
列表 3
<span>package</span> springpack; <span>import</span> ratpack.server.RatpackServer; <span>public</span> <span><span>class</span> <span>Main</span> {</span> <span>public</span> <span>static</span> <span>void</span> <span>main</span>(String[] args) <span>throws</span> Exception { RatpackServer.start(spec -> spec .handlers(chain -> chain .prefix(<span>"api/users"</span>, pchain -> pchain (<span>1</span>) .prefix(<span>":username"</span>, uchain -> uchain (<span>2</span>) .all(ctx -> { (<span>3</span>) String username = ctx.getPathTokens().get(<span>"username"</span>); ctx.byMethod(method -> method (<span>4</span>) .get(() -> ctx.render(<span>"Received request for user: "</span> + username)) .put(() -> { String json = ctx.getRequest().getBody().getText(); ctx.render(<span>"Received update request for user: "</span> + username + <span>", JSON: "</span> + json); }) .delete(() -> ctx.render(<span>"Received delete request for user: "</span> + username)) ); }) ) .all(ctx -> ctx (<span>5</span>) .byMethod(method -> method .post(() -> { (<span>6</span>) String json = ctx.getRequest().getBody().getText(); ctx.render(<span>"Received request to create a new user with JSON: "</span> + json); }) .get(() -> ctx.render(<span>"Received request to list all users"</span>)) (<span>7</span>) ) ) ) ) ); } }
重新构造后的 API 遵循了面向资源的模式,围绕着 user 领域对象为中心。以下是一些修改点:
- 在(1)中我们修改了入口级前缀为 /api/users;
- 在(2)中我们绑定了一个新的前缀处理器到:username 路径变量上。任何到来的请求路径中的值会被转换,并且 Ratpack 处理器可以通过 ctx.getPathTokens() 中的表来访问该值。
- 在(3)中我们为所有匹配 /api/users/:username URI 模式的请求绑定一个处理器;
- 在(4)中我们使用 byMethod 机制来为 HTTP GET,PUT 和 DELETE 方法绑定处理器。通过这些处理器我们可以了解客户端对指定用户的操作意图。在 PUT 处理器中,我们调用 ctx.getRequest().getBody().getText() 方法来捕获到来的请求中的 JSON 数据;
- 在(5)中我们附加一个处理器来匹配所有从 /api/users 端口到来的请求;
- 在(6)中我们对 /api/users 处理器使用 byMethod 机制来附加一个 POST 处理器,当创建新用户时该 POST 处理器会被调用。这里又一次从到来的请求中取出 JSON 数据;
- 最后在(7)中,我们附加了一个 GET 处理器,当客户端需要所有用户的列表时可以调用它。
再次启动该应用程序并进行一系列 curl 命令行调用,来测试这些端口操作是否符合预期:
- 命令:curl http://localhost:5050/api/users , 期望结果:”Received request to list all users”
- 命令: curl -d ‘{ “username”: “dan”, “email”: “danielpwoods@gmail.com” }’ http://localhost:5050/api/users , 期望结果: “Received request to create a new user with JSON: { “username”: “dan”, “email”: “danielpwoods@gmail.com” }”
- 命令: curl http://localhost:5050/api/users/dan , 期望结果: “Received request for user: dan”
- 命令: curl -XPUT -d ‘{ “email”: “daniel.p.woods@gmail.com” }’ http://localhost:5050/api/users/dan , 期望结果: “Received update request for user: dan, JSON: { “email”: “daniel.p.woods@gmail.com” }”
- 命令: curl -XDELETE http://localhost:5050/api/users/dan , 期望结果: “Received delete request for user: dan”
现在我们拥有了满足需求的 API 的基础框架,但仍需使其更加有用。我们可以开始设置服务层的依赖。在本例中,我们将使用 Spring Data JPA 组件作为数据访问对象;列表 4 展示了对构建脚本的修改。
列表 4
buildscript { repositories { jcenter() } dependencies { classpath <span>'io.ratpack:ratpack-gradle:0.9.18'</span> } } apply plugin: <span>'io.ratpack.ratpack-java'</span> apply plugin: <span>'idea'</span> apply plugin: <span>'eclipse'</span> repositories { jcenter() } dependencies { compile ratpack.dependency(<span>'spring-boot'</span>) compile <span>'org.springframework.boot:spring-boot-starter-data-jpa:1.2.4.RELEASE'</span> (<span>1</span>) compile <span>'com.h2database:h2:1.4.187'</span> (<span>2</span>) } mainClassName = <span>"springpack.Main"</span> eclipse { classpath { containers.remove(<span>'org.eclipse.jdt.launching.JRE_CONTAINER'</span>) containers <span>'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'</span> } }
在(1)中,我们引入了对 Spring Boot Spring Data JPA 的依赖,(2) 中我们引入了 H2 嵌入式数据库依赖。总共就这么点修改。当在 classpath 中发现 H2 时,Spring Boot 将自动配置 Spring Data 来使用它作为内存数据源。通过该页面可以详细了解如何配置和使用 Spring Data 数据源。
有了新的依赖后,我们必须做的第一件事是建模我们的微服务领域对象:User。User 类为了演示的目的尽可能的简单,列表 5 展示了一个正确建模的 JPA 领域实体。我们将其放置到项目的 src/main/java/springpack/model/User.java 类文件中。
列表 5
<span>package</span> springpack.model; <span>import</span> javax.persistence.Column; <span>import</span> javax.persistence.Entity; <span>import</span> javax.persistence.GeneratedValue; <span>import</span> javax.persistence.Id; <span>@Entity</span> <span>public</span> <span><span>class</span> <span>User</span> {</span> <span>private</span> <span>static</span> <span>final</span> <span>long</span> serialVersionUID = <span>1</span>l; <span>@Id</span> <span>@GeneratedValue</span> <span>private</span> Long id; <span>@Column</span>(nullable = <span>false</span>) <span>private</span> String username; <span>@Column</span>(nullable = <span>false</span>) <span>private</span> String email; <span>public</span> Long <span>getId</span>() { <span>return</span> id; } <span>public</span> <span>void</span> <span>setId</span>(Long id) { <span>this</span>.id = id; } <span>public</span> String <span>getUsername</span>() { <span>return</span> username; } <span>public</span> <span>void</span> <span>setUsername</span>(String username) { <span>this</span>.username = username; } <span>public</span> String <span>getEmail</span>() { <span>return</span> email; } <span>public</span> <span>void</span> <span>setEmail</span>(String email) { <span>this</span>.email = email; } }
由于 Spring Data 已经处于该项目的编译时需要的 classpath 中,所以我们可以使用 javax.persistence.* 注解。Spring Boot 使用该注解可以实现与数据访问对象一起设置及运行,所以我们可以使用 Spring Data 的脚手架功能中的 Repository 服务类型来模块化 DAO。由于我们的 API 相对来说只是直接的 CRUD 操作,所以我们实现 UserRepository DAO 时,可以利用 Spring Data 提供的 CrudRepository 固定层来编写尽可能少的代码。
列表 6
<span>package</span> springpack.model; <span>import</span> org.springframework.data.repository.CrudRepository; <span>import</span> org.springframework.stereotype.Repository; <span>@Repository</span> <span>public</span> <span><span>interface</span> <span>UserRepository</span> <span>extends</span> <span>CrudRepository</span><<span>User</span>, <span>Long</span>> {</span> User findByUsername(String username); (<span>1</span>) }
惊奇的是,列表 6 中展示的 UesrRepository DAO 实现短短一行代码已经对 User 领域对象实现了一个必要的完全成形的服务层。Spring Data 提供的 Repository 接口允许基于我们对搜索的实体的约定创建”helper”查找方法。根据需求,我们知道 API 层需要通过用户名查找用户,所以可以在(1)处添加 findByUsername 方法。我们把该 UserRepository 放置到项目中的 /src/main/java/springpack/model/UserRepository.java 类文件中。
在修改 API 来使用 UserRepository 之前,我们首先必须定义 Spring Boot 应用程序类。该类代表了一个配置入口,指向了 Spring Boot 自动配置引擎,并且可以构造一个 Spring ApplicationContext,从而可以使用 Ratpack 应用程序中的注册器回调。列表 7 描述了该 Spring Boot 配置类。
列表 7
<span>package</span> springpack; <span>import</span> com.fasterxml.jackson.databind.ObjectMapper; <span>import</span> org.springframework.boot.autoconfigure.SpringBootApplication; <span>import</span> org.springframework.context.annotation.Bean; <span>@SpringBootApplication</span> <span>public</span> <span><span>class</span> <span>SpringBootConfig</span> {</span> <span>@Bean</span> ObjectMapper objectMapper() { (<span>1</span>) <span>return</span> <span>new</span> ObjectMapper(); } }
SpringBootConfig 类中短小精悍的代码放置在 src/main/java/springpack/SpringBootConfig.java 类文件中。在该类中我们显式地自动配置了 Jackson OjbectMapper 的 Spring bean。我们将在 API 层使用它来读写 JSON 数据。
@SpringBootApplication 注解做了大部分事情。当初始化 Spring Boot 注册器回调时,该类会作为入口点。它的基础设施将使用该注解来扫描 classpath 中任何可用的组件,并自动装配这些组件到应用程序上下文中中,并且根据 Spring Boot 的约定规则来自动配置它们。例如,UserRepository 类(使用了 @Repository 注解)存在于应用程序 classpath 中,所以 Spring Boot 将使用 Spring Data 引擎代理该接口,并配置其与 H2 嵌入式数据库一块工作,因为 H2 也在 classpath 中。借助 Spring Boot 我们无需其它多余的配置。
在实现 API 层之前我们需要做的另一个事情是构造 Ratpack 来使用 Spring Boot 应用程序作为注册器。Ratpack 的 Spring Boot 集成组件提供了一个固定层来无缝转换 Spring Boot 应用程序为注册器回调程序,只需一行代码就可以合并这两个世界。列表 8 中的代码展示了更新后的主类,这次使用 SpringBootConfig 类作为 API 层的注册器。
列表 8
<span>package</span> springpack; <span>import</span> ratpack.server.RatpackServer; <span>import</span> ratpack.spring.Spring; <span>import</span> springpack.config.SpringBootConfig; <span>public</span> <span><span>class</span> <span>Main</span> {</span> <span>public</span> <span>static</span> <span>void</span> <span>main</span>(String[] args) <span>throws</span> Exception { RatpackServer.start(spec -> spec .registry(Spring.spring(SpringBootConfig.class)) (<span>1</span>) .handlers(chain -> chain .prefix(<span>"api/users"</span>, pchain -> pchain .prefix(<span>":username"</span>, uchain -> uchain .all(ctx -> { String username = ctx.getPathTokens().get(<span>"username"</span>); ctx.byMethod(method -> method .get(() -> ctx.render(<span>"Received request for user: "</span> + username)) .put(() -> { String json = ctx.getRequest().getBody().getText(); ctx.render(<span>"Received update request for user: "</span> + username + <span>", JSON: "</span> + json); }) .delete(() -> ctx.render(<span>"Received delete request for user: "</span> + username)) ); }) ) .all(ctx -> ctx .byMethod(method -> method .post(() -> { String json = ctx.getRequest().getBody().getText(); ctx.render(<span>"Received request to create a new user with JSON: "</span> + json); }) .get(() -> ctx.render(<span>"Received request to list all users"</span>)) ) ) ) ) ); } }
唯一需要的修改是(1),我们通过一个显式的 Registry 实现提供了对 Ratpack 应用程序的定义。现在我们可以开始实现 API 层。
如果你仔细观察接下来的修改,就会理解 Ratpack 与传统的基于 servlet 的 web 应用是完全不同的。之前我们提及过,Ratpack 的 HTTP 层构建在非阻塞的网络接口上,该 web 框架天然支持高性能。而基于 servlet 的 web 应用会为每个到来的请求产生一个新的线程,虽然会降低资源利用率,但每个请求处理流时是隔离的。在这种机制下,web 应用处理请求时会采用阻塞式的方式,比如调用数据库并等待对应的结果然后返回,在等待期间(相对来说)并不关心这会影响它服务接下来的客户端的能力。在非阻塞式的 web 应用中,如果客户端或服务器端不发送数据,那么网络层并不会被阻塞,所以线程池中少量的“请求任务”线程就可以服务大量高并发的请求。然而这意味着如果应用程序代码阻塞了一个“请求任务”线程,那么吞吐量会显著影响。因此,阻塞操作(比如对数据库的操作)不能放置在请求线程中。
幸运的是,Ratpack 通过在请求上下文中暴露一个阻塞接口来在应用程序中执行阻塞操作。该接口会把阻塞操作放置到另一个不同的线程池中,在维持高容量的情况服务新带来的请求的同时,这些阻塞调用也可以同步完成。一旦阻塞调用完成,处理流会返回到“请求任务”线程中,应答会被写回到客户端。在我们构建的 API 层中,我们要确保所有对 UserRepository 的操作都被路由到阻塞固定层中。列表 9 展示了 API 层的实现。
列表 9
<span>package</span> springpack; <span>import</span> com.fasterxml.jackson.core.JsonProcessingException; <span>import</span> com.fasterxml.jackson.core.type.TypeReference; <span>import</span> com.fasterxml.jackson.databind.ObjectMapper; <span>import</span> ratpack.exec.Promise; <span>import</span> ratpack.handling.Context; <span>import</span> ratpack.server.RatpackServer; <span>import</span> ratpack.spring.Spring; <span>import</span> springpack.model.User; <span>import</span> springpack.model.UserRepository; <span>import</span> java.util.HashMap; <span>import</span> java.util.Map; <span>public</span> <span><span>class</span> <span>Main</span> {</span> <span>private</span> <span>static</span> <span>final</span> Map<String, String> NOT_FOUND = <span>new</span> HashMap<String, String>() {{ put(<span>"status"</span>, <span>"404"</span>); put(<span>"message"</span>, <span>"NOT FOUND"</span>); }}; <span>private</span> <span>static</span> <span>final</span> Map<String, String> NO_EMAIL = <span>new</span> HashMap<String, String>() {{ put(<span>"status"</span>, <span>"400"</span>); put(<span>"message"</span>, <span>"NO EMAIL ADDRESS SUPPLIED"</span>); }}; <span>public</span> <span>static</span> <span>void</span> <span>main</span>(String[] args) <span>throws</span> Exception { RatpackServer.start(spec -> spec .registry(Spring.spring(SpringBootConfig.class)) .handlers(chain -> chain .prefix(<span>"api/users"</span>, pchain -> pchain .prefix(<span>":username"</span>, uchain -> uchain .all(ctx -> { String username = ctx.getPathTokens().get(<span>"username"</span>); UserRepository userRepository = ctx.get(UserRepository.class); ObjectMapper mapper = ctx.get(ObjectMapper.class); Promise<User> userPromise = ctx.blocking(() -> userRepository.findByUsername(username)); ctx.byMethod(method -> method .get(() -> userPromise.then(user -> sendUser(ctx, user)) ) .put(() -> { String json = ctx.getRequest().getBody().getText(); Map<String, String> body = mapper.readValue(json, <span>new</span> TypeReference<Map<String, String>>() { }); <span>if</span> (body.containsKey(<span>"email"</span>)) { userPromise .map(user -> { user.setEmail(body.get(<span>"email"</span>)); <span>return</span> user; }) .blockingMap(userRepository::save) .then(u1 -> sendUser(ctx, u1)); } <span>else</span> { ctx.getResponse().status(<span>400</span>); ctx.getResponse().send(mapper.writeValueAsBytes(NO_EMAIL)); } }) .delete(() -> userPromise .blockingMap(user -> { userRepository.delete(user); <span>return</span> <span>null</span>; }) .then(user -> { ctx.getResponse().status(<span>204</span>); ctx.getResponse().send(); }) ) ); }) ) .all(ctx -> { UserRepository userRepository = ctx.get(UserRepository.class); ObjectMapper mapper = ctx.get(ObjectMapper.class); ctx.byMethod(method -> method .post(() -> { String json = ctx.getRequest().getBody().getText(); User user = mapper.readValue(json, User.class); ctx.blocking(() -> userRepository.save(user)) .then(u1 -> sendUser(ctx, u1)); }) .get(() -> ctx.blocking(userRepository::findAll) .then(users -> { ctx.getResponse().contentType(<span>"application/json"</span>); ctx.getResponse().send(mapper.writeValueAsBytes(users)); }) ) ); }) ) ) ); } {1} <span>private</span> <span>static</span> <span>void</span> <span>notFound</span>(Context context) { ObjectMapper mapper = context.get(ObjectMapper.class); context.getResponse().status(<span>404</span>); <span>try</span> { context.getResponse().send(mapper.writeValueAsBytes(NOT_FOUND)); } <span>catch</span> (JsonProcessingException e) { context.getResponse().send(); } } {1} <span>private</span> <span>static</span> <span>void</span> <span>sendUser</span>(Context context, User user) { <span>if</span> (user == <span>null</span>) { notFound(context); } {1} ObjectMapper mapper = context.get(ObjectMapper.class); context.getResponse().contentType(<span>"application/json"</span>); <span>try</span> { context.getResponse().send(mapper.writeValueAsBytes(user)); } <span>catch</span> (JsonProcessingException e) { context.getResponse().status(<span>500</span>); context.getResponse().send(<span>"Error serializing user to JSON"</span>); } } } {1} {1}
API 层最值得关注的点是对阻塞机制的使用,这次阻塞操作可以从每个请求的 Conext 对象中抽取出来。当调用 ctx.blocking() 方法时,会返回一个 Promise 对象,我们必须订阅该对象以便执行代码。我们可以抽取一个 promise(在 prefix(“:username”) 中展示的一样)从而在不同的处理器中重用,保持代码简洁。
现在实现了 API 后,可以运行一系列 curl 测试来确保该微服务符合预期:
- 命令: curl -d ‘{“username”: “dan”, “email”: “danielpwoods@gmail.com”}’ http://localhost:5050/api/users , 期望结果: {“id”:1,”username”:”dan”,”email”:”danielpwoods@gmail.com”}
- 命令: curl http://localhost:5050/api/users , 期望结果: [{“id”:1,”username”:”dan”,”email”:”danielpwoods@gmail.com”}]
- 命令: curl -XPUT -d ‘{ “email”: “daniel.p.woods@gmail.com” }’ http://localhost:5050/api/users/dan , 期望结果: {“id”:1,”username”:”dan”,”email”:”daniel.p.woods@gmail.com”}
- 命令: curl http://localhost:5050/api/users/dan , 期望结果: {“id”:1,”username”:”dan”,”email”:”daniel.p.woods@gmail.com”}
- 命令: curl -XDELETE http://localhost:5050/api/users/dan , 期望结果: empty
- 命令: curl http://localhost:5050/api/users/dan , 期望结果: {“message”:”NOT FOUND”,”status”:”404”}
通过上面的命令序列可以看出 API 层工作完全正确,我们拥有了一个完全正式的数据驱动的基于 Ratpack 和 Spring Boot 的微服务,并且使用了 Spring Data JPA!
整个过程的最后一步是部署。部署的最简单方式是执行 gradle installDist 命令。这会打包应用程序以及整个运行时依赖到一个 traball(.tar 文件) 和 zip(.zip 文件) 存档文件中。它另外也会创建跨平台的启动脚本,可以在任何安装了 Java 8 的系统中启动我们的微服务。当 installDist 任务完成后,可以在项目的 build/distributions 目录中找到这些存档文件。
通过本文章你已经学会了如何利用 Spring Boot 提供的大量生态系统以及 Ratpack 提供的高性能特性来打造一个微服务应用程序。你可以使用该示例作为起点来构建 JVM 上原生支持云的数据驱动的微服务程序。
欢迎使用 Ratpack 和 Srping Boot!
关于作者
Daniel Woods醉心于企业级 Java、Groovy 以及 Grails 开发。他在 JVM 软件开发领域拥有 10 余年的工作经验,并且乐于向开源项目(比如 Grails 和 Ratpack )贡献他的经验。Dan 曾在 Gr8conf 和 SpringOne 2GX 大会上做过演讲嘉宾,展示了他基于 JVM 的企业级应用程序架构的专业知识。
查看英文原文: Build High Performance JVM Microservices with Ratpack & Spring Boot
感谢张龙对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们,并与我们的编辑和其他读者朋友交流(欢迎加入 InfoQ 读者交流群(已满),InfoQ 读者交流群(#2))。
评论