@contribute
2016-05-19T09:48:09.000000Z
字数 8570
阅读 4459
tinkerpop
Vert.x services 是一种简单有效的方式去封装能重用的功能以便能到处使用。服务使用service的唯一标识符来部署,这能从具体的实现中解耦,体现“低耦合”的设计原则。
打包和部署vert.x的独立服务。
vert.x service factory 是VerticleFactory 的一个实现,能根据服务id部署一个verticle 。
注意这个factory与vert.x服务代理没有直接关系,而是一个关于部署单个组件的设施。
服务名被用于查找JSON描述符文件,JSON描述符文件决定实际被部署的verticle,包含部署参数例如是否作为一个worker来运行等等。
服务使用者从实际被部署的verticle中解耦是很有用的,并且允许服务提供默认的部署参数和配置。
服务名字是一个简单的字符串,可以随用户定义,但是建议用户使用域名反转的方式(如java包的定义),这样避免你类路径中的其他服务同名。例如:
- 推荐命名:
com.mycompany.services.clever-db-service
,org.widgets.widget-processor
- 不推荐但是有效的命名:
accounting-service
,foo
当部署一个服务时使用
service:
,选择服务verticle工厂。这个verticle可以通过编程的方式部署,例如:
vertx.deployVerticle("service:com.mycompany.clever-db-service", options);
也可以通过命令行的方式部署:
vertx run service:com.mycompany-clever-db-service
Vert.x 服务要实现
VerticleFactory
,所以需要你的classpath中确保vertx-service-factory
的jar文件。首先你需要添加verticle factory的maven依赖,如果你使用的是fat jar的方式,你可以用如下依赖:
pom.xml
中)
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-service-factory</artifactId>
<version>3.2.1</version>
</dependency>
build.gradle
文件中)
compile 'io.vertx:vertx-service-factory:3.2.1'
你也可以通过编程的方式用registerVerticleFactory方法注册VerticleFactory
实例
vertx.registerVerticleFactory(new ServiceVerticleFactory());
当部署一个服务时,这个服务工厂首先在classpath中查找这个文件描述符。这个文件描述器就是服务名加
.json
。例如:对于一个服务名:
com.mycompany.clever-db-service
,那么他的服务描述符文件为:com.mycompany.clever-db-service.json
文件描述符文件是一个简单的text文件,内容为有效的JSON对象。JSON中至少必须提供
main
属性,用于指定实际被部署的verticle,例如:
{
"main": "com.mycompany.cleverdb.MainVerticle"
}
或者
{
"main": "app.js"
}
或者你甚至可以重定向到一个不同的verticle工厂。例如,这个Maven verticle工厂在运行时动态的从Maven中加载服务:
{
"main": "maven:com.mycompany:clever-db:1,2::clever-db-service"
}
JSON还能提供options
属性,可精确的被映射到DeploymentOptions
对象中。
{
"main": "com.mycompany.cleverdb.MainVerticle",
"options": {
"config" : {
"foo": "bar"
},
"worker": true,
"isolationGroup": "mygroup"
}
}
当使用服务描述符来部署一个服务时,任何属性如worker
,isolationGroup
等不能被在部署时传递的部署参数所覆盖。
但是config
是一个例外。任意的config在部署时传递的任何配置都将覆盖任何呈现在文件描述符文件中对应的属性。
代理允许event bus 服务像本地服务一样被调用。
当编写一个vertx应用时,你可能想将某个功能应用在某处隔离开来,并对其他服务提供服务。
这就是服务代理的目的。它让你在event bus上暴露一个服务,并其他vertx组建使用,有点类似于event bus发布订阅中的address。
服务是通过java接口来描述的,java接口包含的方法遵循异步模式。在底层,你向event bus发送消息,调用服务并获取响应。为了使其更容易使用,他
可以产生一个代理,你可以直接调用这个代理(使用服务接口中的API)。
使用vertx服务代理需要添加如下依赖:
- Maven(在你的pom.xml
文件中)
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-service-proxy</artifactId>
<version>3.2.1</version>
</dependency>
build.gradle
文件中)
compile 'io.vertx:vertx-service-proxy:3.2.1'
注意,服务代理机制依赖于代码生成,所以service接口修改后需重新编译源码。
在不同的编程语言中使用代理,你需要添加此语言的相关依赖,如groovy需添加vertx-lang-groovy
让我们先看看service proxies并了解为什么有用。假设有一个数据库服务暴露在event bus上,你可能会这样做:
JsonObject message = new JsonObject();
message.put("collection", "mycollection")
.put("document", new JsonObject().put("name", "tim"));
DeliveryOptions options = new DeliveryOptions().addHeader("action", "save");
vertx.eventBus().send("database-service-address", message, options, res2 -> {
if (res2.succeeded()) {
// done
} else {
// failure
}
});
用有一定量的模版代码创建一个服务来监听event bus中的消息,然后将其路由到合适的方法并将结果返回到event bus。
如果使用服务代理,你可以避免写许多同样的模版代码而将精力集中在你的服务上。
当你写你的服务代码时,只需在java接口上用@ProxyGen
对其进行注解,例如:
@ProxyGen
public interface SomeDatabaseService {
// A couple of factory methods to create an instance and a proxy
static SomeDatabaseService create(Vertx vertx) {
return new SomeDatabaseServiceImpl(vertx);
}
static SomeDatabaseService createProxy(Vertx vertx,
String address) {
return new SomeDatabaseServiceVertxEBProxy(vertx, address);
}
// Actual service operations here...
void save(String collection, JsonObject document,
Handler<AsyncResult<Void>> resultHandler);
}
有了这个接口,Vertx将产生所有的访问你的服务的模版代码,并且它将为你的服务产生client side proxy,所以你的客户端使用一个相当符合语言习惯的API,而不是手动的制作event bus消息发送。不管你的实际服务在哪个event bus上(甚至不同机器上),client side proxy照样能工作。
也就是说,你可以通过以下方式与你的服务进行交互:
SomeDatabaseService service = SomeDatabaseService.createProxy(vertx,
"database-service-address");
// Save some data in the database - this time using the proxy
service.save("mycollection", new JsonObject().put("name", "tim"), res2 -> {
if (res2.succeeded()) {
// done
}
});
你也可以在vertx支持的任何语言中联合@ProxyGen
和语言API代码生成(@VertxGen
)创建服务,这意味着你通过风格一致的其他语言API与用java编写的一个服务进行交互,不管这个服务在本地还是在event bus的别处。最后别忘了添加相关依赖。
@ProxyGen // Generate service proxies
@VertxGen // Generate the clients
public interface SomeDatabaseService {
// ...
}
想要被service-proxy generation使用,服务接口必须遵循一些规则,首先要遵循异步模式。返回结果的方法需声明Handler<AsyncResult<ResultType>>
。ResultType
可以是另一个代理。
例如:
@ProxyGen
public interface SomeDatabaseService {
// A couple of factory methods to create an instance and a proxy
static SomeDatabaseService create(Vertx vertx) {
return new SomeDatabaseServiceImpl(vertx);
}
static SomeDatabaseService createProxy(Vertx vertx, String address) {
return new SomeDatabaseServiceVertxEBProxy(vertx, address);
}
// A method notifying the completion without a result (void)
void save(String collection, JsonObject document,
Handler<AsyncResult<Void>> result);
// A method providing a result (a json object)
void findOne(String collection, JsonObject query,
Handler<AsyncResult<JsonObject>> result);
// Create a connection
void createConnection(String shoeSize,
Handler<AsyncResult<MyDatabaseConnection>> resultHandler);
}
with:
@ProxyGen
@VertxGen
public interface MyDatabaseConnection {
void insert(JsonObject someData);
void commit(Handler<AsyncResult<Void>> resultHandler);
@ProxyClose
void close();
}
你可以声明一个特殊方法用注解@ProxyClose
注销此代理.当这个方法被调用时,此代理实例被清除。
更多服务接口的限制在下面详解。
服务被@ProxyGen
注解后,会触发服务帮助类的产生。
EventBus
通过消息与服务交互。产生的代理和处理器的命名是在类名的后面加相关的字段,例如,如果一个服务名为MyService
,则处理器命名为:MyServiceProxyHandler
,代理命名为:MyServiceEBProxy
。
codegen 注解处理器是在编译时产生的这些类。这是java编译器一个功能,所以无需额外的步骤,只需正确的配置好编译器。
这是针对Maven的一个配置例子:
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessors>
<annotationProcessor>io.vertx.codegen.CodeGenProcessor</annotationProcessor>
</annotationProcessors>
<compilerArgs>
<arg>-AoutputDirectory=${project.basedir}/src/main</arg>
</compilerArgs>
</configuration>
</plugin>
这个功能被用在Gradle或甚至IDE已经提供了这样的注解处理器。
如果你有了服务接口,编译了源码并产生了相关代理等,你就需要写一些代码将你的服务注册到event bus上。
SomeDatabaseService service = new SomeDatabaseServiceImpl();
// Register the handler
ProxyHelper.registerService(SomeDatabaseService.class, vertx, service,
"database-service-address");
这可以在verticle中完成,或在你的任何代码中。
一旦注册,这个服务就可用了。如果你的应用运行在集群上,则集群中任务一台主机都可访问。
如果想注销这个服务,可使用ProxyHelper.unregisterService方法。
SomeDatabaseService service = new SomeDatabaseServiceImpl();
// Register the handler
MessageConsumer<JsonObject> consumer = ProxyHelper.registerService(SomeDatabaseService.class, vertx, service,
"database-service-address");
// ....
// Unregister your service.
ProxyHelper.unregisterService(consumer);
当你的服务部署后,你可能想去访问它。这时,你需要创建一个代理,而代理的创建可以使用ProxyHelper
类:
SomeDatabaseService service = ProxyHelper.createProxy(SomeDatabaseService.class,
vertx,
"database-service-address");
// or with delivery options:
SomeDatabaseService service2 = ProxyHelper.createProxy(SomeDatabaseService.class,
vertx,
"database-service-address", options);
这第二个方法获取一个DeliveryOptions
实例,在这里可以配置消息传递的相关参数(如timeout)
你也可以使用产生的代理类。这个代理类名是服务接口类加VertxEBProxy
。例如,你的服务接口名为:SomeDatabaseService
,则代理类名为:SomeDatabaseServiceVertxEBProxy
。
一般情况下,服务接口包含了一个createProxy
的静态方法用于创建代理。但这不是必须的:
@ProxyGen
public interface SomeDatabaseService {
// Method to create the proxy.
static SomeDatabaseService createProxy(Vertx vertx, String address) {
return new SomeDatabaseServiceVertxEBProxy(vertx, address);
}
// ...
}
在服务方法中用到的类型和返回值是有限制的,这样容易转化为event bus消息,也能被异步的使用。有:
返回类型
必须是一下其中一个:
@Fluent
和返回这个服务的应用(this
):
@Fluent
SomeDatabaseService doSomething();
这是因为方法不能阻塞而且如果服务是远程的,不可能立即返回结果而不阻塞。
参数类型
JSON
= JsonObject | JsonArray
PRIMITIVE
= 任意原生类型或包装的原生类型。
参数可以是:
JSON
PRIMITIVE
List<JSON>
List<PRIMITIVE>
Set<JSON>
Set<PRIMITIVE>
Map<String, JSON>
Map<String, PRIMITIVE>
@DataObject
注解的类如果一个异步结果要求提供最后一个参数类型Handler<AsyncResult<R>>
。
R
可以是:
JSON
PRIMITIVE
List<JSON>
List<PRIMITIVE>
Set<JSON>
Set<PRIMITIVE>
@DataObject
注解的类重载的方法
服务方法中不能有被重载。
服务代理假定event bus中的消息遵循一定的格式,因此能被用于调用服务。
当然,如果你不愿意,你也可以不用客户代理来访问远程服务。与服务交互被广泛接受的方式是直接在event bus中发送消息。
为了使服务被交互的方式一致,必须遵循一定的消息格式。
格式非常简单:
action
,作为这个行为或操作的名称。JsonObject
,里面不惜包含此操作中所需要的所有操作。举个例子:
Headers:
"action": "save"
Body:
{
"collection", "mycollection",
"document", {
"name": "tim"
}
}
不论有没有用到服务代理,都应该用上面这种方式创建服务,这样允许服务被交互式保持一致性。
在上面的例子中服务代理使用‘action’值应映射到服务接口中的动作行为的方法名,在消息体中的每个[key, value]
映射到行为方法中的[arg_name, arg_value]
。
对于返回值,服务需使用message.reply(…)
方法去回发一个返回值。这个值可以是任务event bus支持的类型。发送失败信号可以使用message.fail(…)
。
如果使用服务代理产生代码会自动为你处理这些。