微服务是一种体系结构样式,用于创建由单独的细粒度服务组成的应用程序,这鼓励了功能分离,可重用性和可伸缩性。 与我们通常构建的单片应用程序相比,这变得越来越流行。 整体组件通常缺乏微服务在将功能组件分离以拥有自己的独立开发团队,生命周期和部署方面的灵活性。
Java仍然是开发人员在开发企业应用程序时使用的最受欢迎的语言。 毫不奇怪,还有许多支持微服务开发的Java框架。 其中, Spring Boot无疑是最受欢迎的选择。 在这些选项中, Ballerina采用了不同的方法来提供专门为优化微服务开发而构建的编程语言(完整披露-我为Ballerina项目做出了贡献)。
在本文中,我将从Spring Boot应用程序实现开始,并展示使用Ballerina实现的相同功能。 我将要实现的示例用例是基于学生注册表的情况。 在这里,内存中的注册表被保留以插入,查找,更新和删除学生记录。 该注册表的接口通过HTTP API。 为了涵盖编写类似服务时最常用的功能,我在用例中添加了以下要求。
- 如果记录不存在,则学生记录查找将返回带有“ HTTP 200 OK”或“ HTTP 404 Not Found”的JSON学生表示形式。
- 学生记录插入将返回“ HTTP 200 OK”,或者如果记录已经存在,则应返回“ HTTP 400 Bad Request”。
- 学生记录更新成功后返回“ HTTP 200 OK”,否则,如果记录不存在,则应返回“ HTTP 400错误请求”。
- 学生记录删除将删除该记录(如果存在),并将始终返回“ HTTP 200 OK”。
- 学生插入和更新模式必须经过验证,并且对于无效输入,请求应失败。 学生的“主要”字段是具有三个值的枚举:“ CS”,“ Physics”和“ Chemistry”,并且其默认值为“ CS”。
让我们首先从Spring Boot实现开始。
Spring Boot实施
我们将使用Gradle设置和构建我们的Java项目。 以下build.gradle文件包含设置典型Spring Boot服务应用程序的基本配置。
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath ( "org.springframework.boot:spring-boot-gradle-plugin:2.2.1.RELEASE" )
}
}
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
bootJar {
baseName = 'student-registry-service'
version = '0.1.0'
}
repositories {
mavenCentral()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile ( "org.springframework.boot:spring-boot-starter-web" )
testCompile( 'org.springframework.boot:spring-boot-starter-test' )
}
创建一个名为“ student_registry”的目录,并放入上述文件。 另外,在此目录内创建目录结构“ src/main/java/org/demo
”。 在类Unix系统中,可以使用以下命令。
mkdir -p src / main / java / org / demo
在“ org.demo” Java包中,我们将拥有三个类。 这些在下面介绍。
应用
以下“ Application.java ”文件是Java应用程序的入口,该文件包含main方法。 这将通过在类路径中找到服务实现来引导Spring应用程序,并初始化所需的组件。
package org.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main (String[] args) {
SpringApplication.run(Application.class, args);
}
}
学生
以下“ Student.java ”文件代表模型类,代表服务中使用的资源信息。
package org.demo;
public class Student {
private String id;
private String name;
private Major major = Major.CS;
public String getId () {
return id;
}
public String getName () {
return name;
}
public Major getMajor () {
return major;
}
public void setId (String id) {
this .id = id;
}
public void setName (String name) {
this .name = name;
}
public void setMajor (Major major) {
this .major = major;
}
}
enum Major {
CS,
Physics,
Chemistry
}
StudentRegistryController
以下“ StudentRegistryController.java ”文件包含表示资源控制器的类,该类用作将消息调度到的目标。 它将包含将请求模式映射到方法的注释。
package org.demo;import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
@RestController
public class StudentRegistryController {
private Map < String , Student> registry = new HashMap<>();
@GetMapping( "/registry/{id}" )
public Student lookupStudent(@PathVariable( "id" ) String id) {
Student student = this .registry.get(id);
if (student == null ) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Student with the given id does not exist" );
}
return student;
}
@PostMapping( "/registry/" )
public void addStudent(@RequestBody Student student) {
if ( this .registry.containsKey(student.getId())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Student with the given id already exists" );
}
this .registry.put(student.getId(), student);
}
@PutMapping( "/registry/" )
public void updateStudent(@RequestBody Student student) {
if (! this .registry.containsKey(student.getId())) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Student with the given id does not exist" );
}
this .registry.put(student.getId(), student);
}
@DeleteMapping( "/registry/{id}" )
public void deleteStudent(@PathVariable( "id" ) String id) {
this .registry.remove(id);
}
}
运行服务
使用Java包中的上述源文件,我们可以通过在基本目录中执行“ gradle build”来构建项目。 创建的jar文件包含所有依赖项和添加的服务实现。 现在,我们可以使用以下命令简单地运行生成的可执行jar文件。
java -jar build / libs / student-registry-service-0.1.0.jar
![](https://i-blog.csdnimg.cn/blog_migrate/abb2a4ac357e8fbe56ed7746e05c1e54.png)
执行将启动其自己的嵌入式Web服务器,并开始向该服务提供请求。
下面显示了与该服务的示例会话。
$ curl -H"Content-Type: application/json" -X POST -d '{"id":"W01", "name":"Jack", "major":"CS"}' http://localhost:8080/registry/
$ curl http://localhost:8080/registry/W01
{ "id" : "W01" , "name" : "Jack" , "major" : "CS" }
$ curl -H "Content-Type: application/json" -X PUT -d '{"id":"W01", "name":"Jack", "major":"Physics"}' http://localhost:8080/registry/
$ curl http://localhost:8080/registry/W01
{ "id" : "W01" , "name" : "Jack" , "major" : "Physics" }
$ curl -X DELETE http://localhost:8080/registry/W01
$ curl http://localhost:8080/registry/W01
{ "timestamp" : "2019-11-27T05:27:40.185+0000" , "status" :404, "error" : "Not Found" , "message" : "Student with the given id does not exist" , "path" : "/registry/1" }
$ curl -H "Content-Type: application/json" -X POST -d '{"id":"W02", "name":"Saman"}' http://localhost:8080/registry/
$ curl http://localhost:8080/registry/W02
{ "id" : "W02" , "name" : "Saman" , "major" : "CS" }
为了查看返回的HTTP状态代码,可以给curl命令赋予“ -v”开关以启用详细模式。
在下一部分中,让我们看看如何使用Ballerina服务实现相同的功能。
芭蕾舞演员执行
Ballerina对其应用程序有两个切入点。 它可以是主要功能或服务。 对于我们的用例,服务直接适用。 让我们看一下下面的“ student_registry.bal”文件中显示的完整的Ballerina实现,以及如何构建和运行该服务。
import ballerina/http;
public const CS = "CS" ;
public const PHYSICS = "Physics" ;
public const CHEMISTRY = "Chemistry" ;
type Major CS|PHYSICS|CHEMISTRY;
type Student record {
string id;
string name;
Major major = CS;
};
map<Student> students = {};
@http:ServiceConfig {
basePath: "/registry"
}
service StudentRegistry on new http:Listener(8080) {
@http:ResourceConfig {
path: "/{id}" ,
methods: [ "GET" ]
}
resource function lookupStudent(http:Caller caller, http:Request request, string id) returns error? {
Student? student = students[id];
if student is () {
check self.respond(caller, "Student with the given id does not exist" , 404);
} else {
check caller->respond(check json.constructFrom(student));
}
}
@http:ResourceConfig {
path: "/" ,
methods: [ "POST" ],
body: "student"
}
resource function addStudent(http:Caller caller, http:Request request, Student student) returns error? {
if students.hasKey(student.id) {
check self.respond(caller, "Student with the given id already exists" , 400);
return;
}
students[student.id] = student;
check caller->ok();
}
@http:ResourceConfig {
path: "/" ,
methods: [ "PUT" ],
body: "student"
}
resource function updateStudent(http:Caller caller, http:Request request, Student student) returns error? {
if !students.hasKey(student.id) {
check self.respond(caller, "Student with the given id does not exist" , 400);
return;
}
students[student.id] = student;
check caller->ok();
}
@http:ResourceConfig {
path: "/{id}" ,
methods: [ "DELETE" ]
}
resource function deleteStudent(http:Caller caller, http:Request request, string id) returns error? {
var res = trap students.remove(id);
check caller->ok();
}
function respond(http:Caller caller, string msg, int sc) returns error? {
http:Response resp = new;
resp.statusCode = sc;
resp.setTextPayload(msg);
check caller->respond(resp);
}
}
可以使用以下命令来构建和运行以上代码。
Ballerina建立student_registry.bal
Ballerina运行student_registry.jar
Ballerina服务实现的功能与早期定义的Spring Boot应用程序相似。 因此,我们可以在测试服务时使用相同的HTTP请求。
现在,让我们分析代码以了解Ballerina服务如何实现Spring Boot服务的各个方面。
资源模型表示
使用“ Student” Java bean类完成的Spring Boot应用程序中的资源模型实现是使用Ballerina中的“ Student”记录类型完成的。 由于这是一种仅需要数据且没有任何行为的结构,因此记录类型正是这种选择。 另外,使用常量值定义了一种新的“主要”类型,以表示学生可能使用的主要值。
这些值之一也作为默认值在“学生”记录类型的“主要”字段中给出。 默认值以及其他记录类型级别的概念(例如可选字段和值)由Ballerina服务的数据绑定操作直接处理。
应用程序和REST控制器
Spring Boot应用程序和REST控制器由Ballerina服务构造表示。 Ballerina服务代表一种网络服务,该网络服务绑定到特定类型的网络侦听器。 在这里,我们使用了HTTP侦听器,但是它也可以是其他类型的网络侦听器,例如gRPC,WebSocket等。
各个资源功能代表传入的各个请求的服务映射。 这些类似于Spring Boot REST Controller方法。 服务和资源级别注释用于在服务上提供其他元数据,例如基本路径,资源路径和受支持的HTTP方法。
另外,在Ballerina中,我们可以访问传入的请求调用者,以便将明确的响应发送回客户端-这是使用caller-> respond()完成的。 这具有用户具有对返回客户的返回通信的完全控制的附加优点。 例如,如果响应通信中存在错误,则可以在Ballerina代码中直接看到并访问该错误。 因此,我们可以提供其他逻辑来处理这种情况。 在Spring Boot代码中,我们返回Java对象,或者抛出异常以向客户端发出返回通信信号。 此后,我们将无法直接访问返回给客户的回程通讯。
图形视图
在Ballerina中,涉及的另一个方面是自动生成代码序列图。 这是可能的,因为Ballerina语言本身是完全基于序列图概念设计的。 在这里,我们所有的客户端,侦听器和远程端点在图中都显示为参与者,并且使用“->”运算符表示的远程函数调用显示了它们之间传递的消息。
![](https://i-blog.csdnimg.cn/blog_migrate/f6c31a7b7b3e16233501a9f1c50c3042.png)
摘要
上面的说明显示了如何将典型的Spring Boot HTTP服务应用程序转换为Ballerina服务的场景。 以下是本文中使用的一些资源,它们对您进一步学习Ballerina很有用:
翻译自: https://hackernoon.com/microservices-wars-spring-boot-vs-ballerina-mx5g37ww