Package: is-wire
is-wirePub/Sub middleware for theisarchitecture (python implementation)InstallationInstall the wire package usingpiporpipenv:pipinstall--useris-wire# orpipenvinstall--useris-wireUsagePrepare environmentIn order to send/receive messages an amqp broker is necessary, to create one simply run:dockerrun-d--rm-p5672:5672-p15672:15672rabbitmq:3.7.6-managementBasic send/receiveCreate a channel to connect to a broker, create a subscription and subscribe to desired topics to receive messages:fromis_wire.coreimportChannel,Subscription# Connect to the brokerchannel=Channel("amqp://guest:guest@localhost:5672")# Subscribe to the desired topic(s)subscription=Subscription(channel)subscription.subscribe(topic="MyTopic.SubTopic")# ... subscription.subscribe(topic="Other.Topic")# Blocks forever waiting for one message from any subscriptionmessage=channel.consume()print(message)Create and publish messages:fromis_wire.coreimportChannel,Message# Connect to the brokerchannel=Channel("amqp://guest:guest@localhost:5672")message=Message()# Body is a binary field therefore we need to encode the stringmessage.body="Hello!".encode('latin1')# Broadcast message to anyone interested (subscribed)channel.publish(message,topic="MyTopic.SubTopic")Serialize/Deserialize protobuf objects:fromis_wire.coreimportChannel,Message,Subscription,ContentTypefromgoogle.protobuf.struct_pb2importStructchannel=Channel("amqp://guest:guest@localhost:5672")subscription=Subscription(channel)subscription.subscribe(topic="MyTopic.SubTopic")struct=Struct()struct.fields["apples"].string_value="red"message=Message()message.content_type=ContentType.JSON# or ContentType.PROTOBUFmessage.pack(struct)# Serialize the struct into the message bodychannel.publish(message,topic="MyTopic.SubTopic")# Blocks forever waiting for the message we just sentreceived_message=channel.consume()# Deserialize the struct from the message bodyreceived_struct=received_message.unpack(Struct)# Check that they are equalassertstruct==received_structBasic Request/ReplyCreate a RPC Server:fromis_wire.coreimportChannel,StatusCode,Statusfromis_wire.rpcimportServiceProvider,LogInterceptorfromgoogle.protobuf.struct_pb2importStructimporttimedefincrement(struct,ctx):ifstruct.fields["value"].number_value<0:# Return error to clientreturnStatus(StatusCode.INVALID_ARGUMENT,"Number must be positive")time.sleep(0.2)# Simulate workstruct.fields["value"].number_value+=1.0# Return normal replyreturnstructchannel=Channel("amqp://guest:guest@localhost:5672")provider=ServiceProvider(channel)logging=LogInterceptor()# Log requests to consoleprovider.add_interceptor(logging)provider.delegate(topic="MyService.Increment",function=increment,request_type=Struct,reply_type=Struct)provider.run()# Blocks forever processing requestsSend a request to the RPC Server:fromis_wire.coreimportChannel,Message,Subscriptionfromgoogle.protobuf.struct_pb2importStructimportsocketchannel=Channel("amqp://guest:guest@localhost:5672")subscription=Subscription(channel)# Prepare requeststruct=Struct()struct.fields["value"].number_value=1.0request=Message(content=struct,reply_to=subscription)# Make requestchannel.publish(request,topic="MyService.Increment")# Wait for reply with 1.0 seconds timeouttry:reply=channel.consume(timeout=1.0)struct=reply.unpack(Struct)print('RPC Status:',reply.status,'\nReply:',struct)exceptsocket.timeout:print('No reply :(')Multiples requests can be done throughout same client. To distinguish which reply is related to each request, you can use thecorrelation_id. This attribute is always set when aMessageis published containingreply_toparameter, which means that it was a RPC request. Example below shows how to deal with it.fromis_wire.coreimportChannel,Message,Subscriptionfromgoogle.protobuf.struct_pb2importStructimportsocketchannel=Channel("amqp://guest:guest@localhost:5672")subscription=Subscription(channel)# Prepare first requeststruct=Struct()struct.fields["value"].number_value=1.0request_1=Message(content=struct,reply_to=subscription)# Prepare second requeststruct=Struct()struct.fields["value"].number_value=2.0request_2=Message(content=struct,reply_to=subscription)# Make requestschannel.publish(request_1,topic="MyService.Increment")channel.publish(request_2,topic="MyService.Increment")# Wait for replies with 1.0 seconds timeoutn_replies=0whilen_replies<2:try:reply=channel.consume(timeout=1.0)struct=reply.unpack(Struct)ifreply.correlation_id==request_1.correlation_id:n_replies+=1print('First Request\nRPC Status:',reply.status,'\nReply:',struct)elifreply.correlation_id==request_2.correlation_id:n_replies+=1print('Second Request\nRPC Status:',reply.status,'\nReply:',struct)else:print('Unexpected message')exceptsocket.timeout:print('No reply :(')Tracing messagesThis middleware usesopencensusas instrumentation library. Latest versions of opencensus released separate packages to integrate with different frameworks and tracing collector tools. When interacting with services implemented with either the C++ or Python of is-wire, we recommend to useZipkinto collect the tracing data. To do so, use the latest version ofOpenCensus Zipkin Exporter.Instantiate an Exporter to trace requests:fromis_wire.coreimportAsyncTransportfromopencensus.ext.zipkin.trace_exporterimportZipkinExporter# Create an exporter, change values accordingly to match your zipkin serverexporter=ZipkinExporter(service_name="MyService",host_name="localhost",port=9411,transport=AsyncTransport,)Then create a tracer and start tracing:fromis_wire.coreimportChannel,Message,Tracerchannel=Channel("amqp://guest:guest@localhost:5672")tracer=Tracer(exporter)withtracer.span(name="publish")asspan:message=Message()# ...# Propagates the current tracing contextmessage.inject_tracing(span)channel.publish(message,topic="Any.Topic")Or create a tracing interceptor and pass it to your ServiceProvider:fromis_wire.rpcimportTracingInterceptor,ServiceProviderchannel=Channel("amqp://guest:guest@localhost:5672")provider=ServiceProvider(channel)tracing=TracingInterceptor(exporter)# automatically trace requestsprovider.add_interceptor(tracing)DevelopmentTests# prepare environmentpipinstall--usertox
dockerrun-d--rm-p5672:5672-p15672:15672rabbitmq:3.7.6-management# run all the teststox