Seamless Talk (RabbitMQ) is a java library for easy and fast developing microservices' communication on the RabbitMQ bus. It wraps Spring AMQP mechanics by some automation, making it easier to develop software and waste less time to deal with RabbitMQ.
Main features:
- queues and exchanges auto-generation and binding, based on your configuration and namings
- java API code generation to communicate between services
- seamless exceptions transferring by RabbitMQ
This step-by-step guide will quickly get you started on SeamlessTalk (RabbitMQ) basics.
You need to know some basics about this library:
Contract- is an interface marked with @SeamlessTalkRabbitContract annotationListener- is a class implementing the contract and marked with @SeamlessTalkRabbitListener, where every interface's overridden method must be marked with @RabbitHandler.Generated API- is a class that is auto-generated by the library, implementing the same contractDTO (Data transfer object)- a common class for data transferring between modules. I hope you knew it, but still, I have to name it.
Also, you need to have a RabbitMQ instance to connect. For example, you can up a ready docker image. Default settings will be enough for this guide, so just use this command below:
docker run --name rabbit-management -p 15672:15672 -p 5672:5672 rabbitmq:3-management
More information can find at RabbitMQ DockerHub page.
You need to create a shared library for all your services. It will contain common objects (ex. contracts and DTOs). It needs to exist because all data will be serialized and deserialized using the full class name, and it shall be the same everywhere.
In this example, it will be called shared-lib. Also let's create two microservice modules: library-api, and repository-manager. The
first one will implement REST controllers to send requests by RabbitMQ to the second one.
Also, you need to define RabbitMQ connection parameters in every microservice's properties. The most common setup will be:
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
More information can find at Spring AMQP docs.
When the modules have been created, you need to define your contracts. There are a few steps to take it:
- create an interface with the name of the contract. Usually, it ends with '*Contract'. Ex. LibrarySearchContract.
- mark it with @SeamlessTalkRabbitContract and set desired annotation arguments, ex. exchangeType (direct or fanout).
- define contract methods inside. Method parameter means incoming payload type and returns value - outgoing.
- Due to implementation features of
AMQP, there is a single payload in the message. So, you can put only one argument to contract methods. So, it requires creating a DTO with all arguments you need inside.
For example, let's create a contract LibraryIOContract with two methods:
- FileDto get(String path)
- boolean save(FileDto request)
You will have such code:
@SeamlessTalkRabbitContract
public interface LibraryIOContract {
FileDto get(String path);
boolean save(FileDto request);
}Where FileDto is a common class defined in `share-lib:
@Data
@NoArgsConstructor
@SuperBuilder
public class FileDto {
private byte[] data;
private String path;
}I hope you don't have anything against Lombok :)
Now you can run the build task and see how will be auto-generated API class implementing this contract. Like this:
@Service
@Primary
public class LibraryIOContractRabbitApi extends AbstractRabbitApi implements LibraryIOContract {
private AmqpTemplate amqpTemplate;
private Binding binding;
public LibraryIOContractRabbitApi(AmqpTemplate amqpTemplate, RoutesGenerator routesGenerator) {
this.amqpTemplate = amqpTemplate;
this.binding = routesGenerator.getBinding(LibraryIOContract.class);
}
@Override
public FileDto get(String payload) {
return convertSendAndReceive(amqpTemplate, binding.getExchange(), binding.getRoutingKey(), payload);
}
@Override
public boolean save(FileDto payload) {
return convertSendAndReceive(amqpTemplate, binding.getExchange(), binding.getRoutingKey(), payload);
}
}The mark that you set up all correctly will be in build logs:
> Task :examples-2-ms-with-shared-lib-shared-lib:compileJava
Note: Started Seamless Talk rabbit api generation for found contracts:
LibraryIOContract
Now all preparations in the shared library have been done. Let's go to the microservices.
You need to create a class implementing the desired contract and mark it with @SeamlessTalkRabbitListener.
Let's create a listener inside the repository-manager module. Like this:
@SeamlessTalkRabbitListener
public class LibraryIOListener implements LibraryIOContract {
@Override
@RabbitHandler
public FileDto get(String filePath) {
return FileDto.builder()
.data("answer".getBytes())
.path("anotherSomePath")
.build();
}
@Override
@RabbitHandler
public boolean save(FileDto request) {
throw new NotImplementedException();
}
}That's all you need to do. Let's try to use it,
Let's go to the library-api module and create a REST-controller with an autowired LibrarySearchContract instance.
@RestController
@RequestMapping("library")
public class LibraryController {
@Autowired
private LibrarySearchContract librarySearchContract;
...
}After it let's add some methods inside to call our contract functionality:
...
@RequestMapping("/get")
FileDto get(){
return libraryIOContract.get("somePath");
}
@RequestMapping(path = "/save")
boolean save() {
return libraryIOContract.save(FileDto.builder()
.data("someData".getBytes())
.path("somePath")
.build());
}
...Let's run both microservices and request the get REST method.
curl -X GET http://localhost:9002/library/get
If it returns such response, then you made all correctly, and your the connection between microservices set up.
HTTP/1.1 200
connection: keep-alive
content-type: application/json
date: Tue, 22 Mar 2022 08:37:11 GMT
keep-alive: timeout=60
transfer-encoding: chunked
{"data":"YW5zd2Vy","path":"anotherSomePath"}
Also, you can request the save REST method and get an error.
curl -X POST http://localhost:9002/library/save
HTTP/1.1 500
connection: close
content-length: 0
date: Tue, 22 Mar 2022 08:38:06 GMT
Let's look up at logs in library-api and see that the NotImplementedException thrown in repository-manager was
fully transferred to library-api module and re-thrown there as usual exception:
...
com.coddweaver.seamless.talk.rabbit.exceptions.NotImplementedException: null
at com.coddweaver.seamless.talk.rabbit.repositorymanager.listeners.LibraryIOListener.get(LibraryIOListener.java:22) ~[main/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
...
That's all you need to know to create a simple connection between microservices using SeamlessTalk (RabbitMQ). More complex examples you can
find in examples folder.