16.4 GenAvro(Avro IDL)语言
为了让开发者在声明模式时使用一种与诸如Java、C++、Python等普通编程语言相似的方法,Avro提供了GenAvro语言。GenAvro是声明Avro模式的高级语言(最新版本中称为Avro IDL),虽然它目前还没有完全确定下来,但不会有主要的变化。之前在其他构架如Thrift、Protocol、CORBA中使用过接口描述语言(IDL)的开发者可能会对Avro IDL语言有亲切感。
每个Avro IDL文件定义了单一的Avro协议,并产生一个JSON格式的Avro协议文件,其扩展名为.avpr。为了使Avro IDL(新版本中为.avdl)文件转化为.avpr文件,必须使用IDL工具进行处理,例如:
$java-jar avroj-tools.jar idl src/test/idl/input/namespaces.avdl/tmp/namespaces.avpr
$head/tmp/namespaces.avpr
{
"protocol":"TestNamespace",
"namespace":"avro.test.protocol",
……
这个IDL工具也可以处理从stdin输入的数据或输出到stdout的数据,更多的信息可以用idl—help命令查询。一个Avro IDL文件只包含一个协议定义,较小的协议可由以下代码定义:
protocol MyProtocol{
}
这相当于以下的JSON协议定义:
{
"protocol":"MyProtocol",
"types":[],
"messages":{
}
}
使用@namespace注解后,协议的命名空间可能会改变,代码如下:
@namespace("mynamespace")
protocol MyProtocol{
}
在Avro IDL中,可以通过使用@namespace为所注解的元素指定属性。Avro IDL中的协议包含以下项目:
指定模式的定义,包括记录、错误、枚举和固定型。
RPC消息的定义。
外部协议和模式文件的引用。
引入文件可以用以下三种方式之一:
引入IDL文件使用语句import idl“foo.avdl”。
引入JSON协议文件使用语句import protocol“foo.avpr”。
引入JSON模式文件使用语句import schema“foo.avsc”。
下面介绍各种类型的定义方法。
1)定义枚举。在Avro IDL中使用类似于C或Java的语法来定义枚举,代码如下:
enum Suit{
SPADES, DIAMONDS, CLUBS, HEARTS
}
需要注意的是,不像JSON格式,在Avro IDL中匿名的枚举是无法定义的。
2)定义固定长度的字段。定义一个固定长度的字段可以使用以下语法:
fixed MD5(16);
该例子定义了一个包含16字节名称为MD5的固定长度类型。
3)定义记录和错误。在Avro IDL中定义记录的语法类似于C中的结构体定义,代码如下:
record Employee{
string name;
boolean active;
long salary;
}
以上例子定义了一个带有三个字段称为“Employee”的记录,错误类型的定义只需要将record改为error就可以了,代码如下:
error Kaboom{
string explanation;
int result_code;
}
记录和错误中的字段包括类型和名称,也可以有属性注解。Avro IDL语言中引用的类型必须为以下之一:
原始类型;
已命名的模式,该模式在同一协议中且使用前已经定义;
复杂类型(数据、映射或者联合)。
下面分别介绍它们。
1)Avro IDL支持的原始类型与Avro的JSON格式支持的类型一样,包括int、long、string、boolean、float、double、null和bytes。
2)如果相同的Avro IDL文件中已经定义了指定的模式且为原始类型,那么可以通过名称直接引用,代码如下:
record Card{
Suit suit;//引用之前定义的枚举类型Card
int number;
}
3)复杂类型。数组类型的定义方法与C++或Java中的定义方式类似。任何类型t的数组写为array<t>。例如,字符串的数组写为array<string>,记录Foo的多维数组写为array<array<Foo>>。映射类型和数组类型相似,包含类型t的数组写为map<t>,和JSON模式格式一样,所有的映射包含string类型的键。联合类型写为union{typeA, typeB, typeC,……},例如,下面这个记录包含可选的字符串字段:
record RecordWithUnion{
union{null, string}optionalString;
}
需要注意的是,Avro IDL中联合的限制与JSON格式的一样,即记录不能包含相同类型的多种元素。
使用Avro IDL协议定义RPC消息的语法与C语言头文件或Java接口的方法声明相似。例如带有参数foo和bar且返回int值的RPC消息定义为:
int add(int foo, int bar);
定义一个没有返回值的消息可以使用别名void,相当于Avro的null类型,如下所示:
void logMessage(string message);
如果在相同的协议之前已经定义了一个错误类型,那么可以使用下面语法声明消息抛出这个错误:
void goKaboom()throws Kaboom;
如果定义一个one-way的消息,只需在参数后面使用关键字oneway,代码如下:
void fireAndForget(string message)oneway;
最后介绍其他的Avro IDL语言特征。
(1)注释
Avro IDL语言支持所有的Java类型注释。每行//后面的内容将被忽略,用/和/可以注释多行内容。
(2)区别标识
当语言需要保留字来作为标识时,需要用符号“”来区别标识。例如,定义一个带有名称error的消息:
voiderror();
这个语法可以使用在任何有标识的地方。
(3)排序和命名空间的注释
在Avro IDL中Java风格的注释可以用来给类型增加额外的属性。例如,指定记录中字段的排序顺序可以使用@order,如下所示:
record MyRecord{
@order("ascending")myAscendingSortField;
@order("descending")myDescendingField;
@order("ignore")myIgnoredField;
}
当然注释也可以放在字段类型的前面,如:
record MyRecord{
@java-class(“java.util.ArrayList”)array string myStrings;
}
类似的,当定义一个指定模式时,使用@namespace可以修改命名空间,如:
@namespace("org.apache.avro.firstNamespace")
protocol MyProto{
@namespace("org.apache.avro.someOtherNamespace")
record Foo{}
record Bar{}
}
这里在firstNamespace命名空间中定义了一个协议,记录Foo定义在someOtherNamespace中,Bar定义在firstNamespace中,且从容器中继承了默认值。
对于类型和字段的别名可以用注释@aliases来指定,如下所示:
@aliases(["org.old.OldRecord","org.ancient.AncientRecord"])
record MyRecord{
string@aliases(["oldField","ancientField"])myNewField;
}
下面是Avro IDL文件的完整例子:
/**
*An example protocol in Avro IDL
*/
@namespace("org.apache.avro.test")
protocol Simple{
@aliases(["org.foo.KindOf"])
enum Kind{
FOO,
BAR,//the bar enum value
BAZ
}
fixed MD5(16);
record TestRecord{
@order("ignore")
string name;
@order("descending")
Kind kind;
MD5 hash;
union{MD5,null}@aliases(["hash"])nullableHash;
array<long>arrayOfLongs;
}
error TestError{
string message;
}
string hello(string greeting);
TestRecord echo(TestRecordrecord);
int add(int arg1,int arg2);
bytes echoBytes(bytes data);
voiderror()throws TestError;
void ping()oneway;
}