本文主要介绍了面向服务的编程语言Jolie是如何构建微服务的。其中内容涵盖了服务编程的四个基本要素:API、服务、访问点端点、逻辑。
Jolie[1]是一门面向服务的编程语言。其中Jolie支持抽象层(该层允许服务使用不同的协议进行通信,范围从TCP/IP、socket到进程之间的本地内存通信)能够很好解决微服务设计和实际中的问题,使得开发更加有效。
步骤一:定义服务API
我们从定义服务的接口开始入手。
首先我们先为从客户端发起的请求定义一个名为GreetingRequest的数据类型,它包含一个名为name的字符串。
type GreetingRequest { name:string } // 请求类型
然后把响应客户端请求的数据也定义一个数据类型,称为:Greeting,它包含一个名为greeting的字符串。
type Greeting { greeting:string } // 响应类型
接下来我们就可以使用我们的数据类型来定义一个接口,接口指定了一些提供给客户端的操作等功能列表。
interface GreeterIface {
RequestResponse: greet(GreetingRequest)(Greeting)
}
以上可以理解为RequestResponse接收操作的请求后,始终将响应发回给客户端(Jolie还提供OneWay作为替代方法,用于简单的通信/通知)。我们这里期望的接收请求类型为GreetingRequest的请求,并发回消息类型为Greeting的响应。
目前到这里我们就完成了定义服务API的部分。
步骤二:定义服务
定义了要实现的接口之后,接下来我们就可以定义服务了。Jolie提供了一个用于定义服务的模块,称为:service。然后我们新建一个叫Greeter服务,可以通过使用execution关键字来指定Greeter服务要同时并发处理的客户端。
service Greeter {
execution: concurrent
/* More will be added here... */
}
定义服务Greeter需要两个基本内容:至少有一个访问端点,用于定义客户端如何访问该服务;行为,定义实际实现该服务提供的API的业务逻辑。我们通过步骤三、步骤四来完成这些内容。
步骤三:定义访问端点
创建访问端点使用的关键字是inputPort,但是要定义一个接入点,就必须要先做以下声明:
- location:指定服务的地址
- protocol:指定与服务交互的协议
- interfaces:指定接口
其中每一个声明在Jolie中都有一个对应的关键字。下面,我们定义一个接入点:
- location:TCP 8080端口上的连接
- protocol:使用HTTP作为传输协议,默认情况下以JSON格式编码数据
- interfaces:指定名为GreeterIface的API
service Greeter {
execution: concurrent
inputPort GreeterInput {
location: "socket://localhost:8080"
protocol: http { format = "json" }
interfaces: GreeterIface
}
/* More will be added here... */
}
步骤四:定义行为(业务逻辑)
现在我们为Greeter服务定义一个行为。我们希望它:
- 从任何客户端接收有关greet的操作请求。在Jolie中,只需编写操作名称即可完成此操作
- 把接收到的客户端请求存在一个变量中,例如request
- 处理包含请求的响应,例如使用名为response的变量
代码如下:
service Greeter {
execution: concurrent
inputPort GreeterInput {
location: "socket://localhost:8080"
protocol: http { format = "json" }
interfaces: GreeterIface
}
main {
greet(request)(response) {
response.greeting = "Hello, " request.name "!"
}
}
}
准备运行服务
在运行服务前,我们先来回顾下到目前为止的代码:
type GreetingRequest { name:string } // The type of greeting requests
type Greeting { greeting:string } // The type of greetings
interface GreeterIface {
RequestResponse: greet(GreetingRequest)(Greeting)
}
service Greeter {
execution: concurrent
inputPort GreeterInput {
location: "socket://localhost:8080"
protocol: http { format = "json" }
interfaces: GreeterIface
}
main {
greet(request)(response) {
response.greeting = "Hello, " request.name "!"
}
}
}
将代码保存在以.ol后缀的文件中,例如main.ol。然后运行以下命令:
jolie main.ol
服务运行后,发起请求,运行以下curl命令:
curl http://localhost:8080/greet?name=Jolie
运行结果:
{"greeting":"Hello, Jolie!"}
了解更多
访问我们的网站,安装Jolie[2],浏览其官方文档[3],并了解如何容器化Jolie服务[4]。
总结:对有效性和技术不可知论的反思
通过这个简单的例子可以说明Jolie几个重要的方面。
一方面,在Jolie中对服务的编码与通常用于服务的概念模型非常相似:我们定义了数据类型(用于数据模型)、接口、服务、访问端点和实现(行为),这有助于提高我们的工作效率!
另一方面,Jolie是为整合而设计的,是一个为技术中不可知论而设计的技术。它的数据类型语言捕获了DTO(数据传输对象),只假设大多数技术中可用的类型(字符串、布尔值等)。表达行为的语言抽象了数据在线上的编码方式:例如,在我们上面写的行为中,并没有说(变量中的数据)请求和响应要用JSON去/编码。这只有在访问点中才能看到,因此我们可以自由地根据自己的需要进行修改。比如说,我们想通过发送以XML而不是JSON编码的请求来访问我们的Greeter服务。我们只需要将Greeter的输入端改为使用XML即可,具体如下:
service Greeter {
execution: concurrent
inputPort GreeterInput {
location: "socket://localhost:8080"
protocol: http { format = "xml" } // < This is the only change
interfaces: GreeterIface
}
main {
greet(request)(response) {
response.greeting = "Hello, " request.name "!"
}
}
}
同样,我们可以一次Greeter通过二进制协议甚至不同种类的协议进行访问。
这些只是Jolie某些设计原则的一些尝试。我们将进行更多探索,并在未来进行更深入的探索!