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;

}