nestjs event-based messaging with 1 producer and 2 consumers - microservices

With nestjs microservices, you can send messages and receive the result using request/response-based approach. That is implemented with a combination of #MessagePattern and client.send('my_pattern', myData). For an example see the nest docs: https://docs.nestjs.com/microservices/basics#request-response and https://docs.nestjs.com/microservices/basics#sending-messages.
How do I receive the result in the event-based approach?
Suppose you have a user microservice and an auth microservice. Whenever a user is created you want an auth subject to be created as well (saving the username and a hash of the password, so that the user may login with an api request to the auth microservice instead of the user service).
auth/auth.controller.ts
#EventPattern('EVT_USER_CREATED')
public async handleUserCreated(data: any): Promise<AuthSubject> {
if (!data.username || !data.password) {
throw new RpcException('Auth subject must supply username and password');
}
const newSubject: CreateAuthSubject = {
username: data.username,
password: data.password,
email: data.email ?? '',
};
const sub = await this.subjectService.create(subject);
return sub;
}
user/user.controller.ts
#Post('')
#ApiBody({ type: CreateUser })
#ApiCreatedResponse({ type: User })
public async create(#Body() user: CreateUser): Promise<User> {
const newUser = await this.userService.create(user);
this.userQueue
.emit<any, CreateUser>('EVT_USER_CREATED', user)
.subscribe((res) => {
console.log(res); // undefined
});
return newUser;
}
To verify that there is no error in my setup, I changed #EventPattern to #MessagePattern and this.userQueue.emit<... to this.userQueue.send<.... It worked, i.e. res was a valid auth subject with username and password as expected. However, with the event-based approach outlined in this question res is always undefined (whether or not the auth controllers handleUserCreated returns or throws).
Ultimately I would like to achieve the following: If another microservice needs to process 'EVT_USER_CREATED' events, I just add a #EventPattern('EVT_USER_CREATED') public async handleUserCreated method to its controller. The observable this.userQueue.emit<any, CreateUser>('EVT_USER_CREATED', user) would then receive both results: Once for each of the microservices consuming the user created event.
So suppose I add a third microservice: the customer microservice that is responsible for saving payment information, the history of orders, etc. Like the auth service it subscribes to 'EVT_USER_CREATED'.
customer/customer.controller.ts
#EventPattern('EVT_USER_CREATED')
public async handleUserCreated(data: any): Promise<AuthSubject> {
const customer = await this.customerService.create(data);
return customer ;
}
Now with the above setup the microservices auth and customer will alternate in receiving the events: If the user microservices emits the creation of a user, only the auth service will react to it and create an auth subject from hat user. No customer will be created for that user. For the next user that is created in the user microservice, only a customer will be created but not an auth subject. The third created user will again be consumed by the auth microservice but not by the customer microservice. And so on.
-- auth microservice
/
user microservice --- message broker ---
\
-- customer microservice
To summarize: How do I achive the messaging architecture shown in the diagram, such that I only need one emit(...) call in the user.controller.ts and, such that I reveive both responses in the subscrption to the emit(...) call?

It may be a bit late but I leave my contribution for future users.
For this type of architecture strongly based on events, it is advisable to use a messaging broker such as Kafka, with which different services can be subscribed to different topics, and even different services can listen to the same topic, in this way you can react in different ways to the same event.
Kafka also offers many advantages that will be useful when you want to scale your microservices, and as an extra, it has support in Nest.
https://docs.nestjs.com/microservices/kafka
What you should keep in mind is that when messaging systems are used, communication is usually completely asynchronous, this is an important detail since, following the case you present, not only is it enough for you to send a message through kafka to another microservice to validate the credentials of a user, but it is necessary to return a response to the client. For this case, the tools offered by nest are limited since using the #MessagePattern decorator, we can receive messages from kafka in our controller, but we cannot wait for a response (of the same topic or another) with the confirmation of success to respond to the Customer request. In this case, it would be convenient for you to create your own transporter or your own kafka client with Kafkajs (https://kafka.js.org/) so that if necessary, you can keep the user's request until you receive a confirmation for another topic.
A solution that I have seen in certain forums is to save an object in memory (key / value) with the "Response" object of a request associated with the id of a user, in this way you can send a message by kafka or any other broker with a specific action, and receive the confirmation by another controller, retrieve the user's "Request" object and then, send the response (Request.send (...)). It is not the ideal solution but it works for some cases.
For example:
#Controller('users')
class MyController {
constructor(private kafkaClient: KafkaClient) {}
private users: new Map<string, Request>() // Or private users: any = {}
onModuleInit() {
// By default, NestJs create topic "users-topic.reply"
this.kafkaClient.subscribeToResponseOf('users-topic')
}
#Post('auth')
authUser(#Req req: Request, #Res res: Response): void {
const userData = req.body;
const { id } = userData;
// Save request object in private variable;
this.users.set(id, res); // or this.users[id] = res;
// Send kafka message to validate user data
this.kafkaClient.emit('users-topic')
}
#MessagePattern('users-topic.reply')
authUser(#Payload() data: any): any {
if (data.message === 'success') {
const res:Request = this.users.get(data.id); // or this.users[data.id];
// Clean users object
this.users.remove(data.id); // or this.users[data.id] = null;
// Response client request
res.send('Authentication successfully')
}
}
}

Related

Spring-Boot - using AWS SQS in a synchronic way

I have a pub/sub scenario, where I create and save to DB something in one service, publish it to a SNS topic, subscribe with SQS listener, and handle the message and save it to DB in another service. So far so good.
In one of the scenarios I create a user and subscribe it to a site. Then I send the new user to its topic, the user-site relation to another topic, and the subscribed service updates its own DB tables.
private void publishNewUserNotifications(UserEntity userEntity, List<SiteEntity> sitesToAssociateWithUser) {
iPublisherService.publishNewUserNotification(userEntity);
if (sitesToAssociateWithUser != null || !sitesToAssociateWithUser.isEmpty()) {
List<String> sitesIds = sitesToAssociateWithUser.stream().map(SiteEntity::getSiteId).collect(Collectors.toList());
iPublisherService.publishSitesToUserAssignment(userEntity.getId(), new ArrayList<>(), sitesIds);
}
}
The problem is that sometimes I have a thread race and handle the user-site relation before I created the user in the second service, get an empty result from DB when loading the User object, and fail to handle the user-site relation.
#Override
#Transactional
public void handle(UsersSitesListNotification message) {
UsersSitesNotification assigned = message.getAssigned();
List<UserEntity> userEntities = iUserRepository.findAllByUserIdIn(CollectionUtils.union(assigned.getUserIds()));
List<SiteEntity> siteEntities = iSiteRepository.findAllByIdIn(CollectionUtils.union(assigned.getSiteIds()));
List<UserSiteAssignmentEntity> assignedEntities = fromUsersSitesNotificationToUserSiteAssignmentEntities(assigned, userEntities, siteEntities);
Iterable<UserSiteAssignmentEntity> saved = iUserSiteAssignmentRepository.saveAll(assignedEntities);
}
Because of that, I consider using SQS in a synchronic way. The problem is that in order to use SQS I need to import the "spring-cloud-aws-messaging" package, and the SQS configuration inside it uses the Async client.
Is there a way to use SQS in a synchronic way? What should I change? How should I override the Async configuration that I need in the package/get some other package?
Any idea will help, tnx.

How to get the result of a payable transaction using near-api-js?

When calling a contract method with attached deposits, you are redirected to the NEAR wallet for approving the transaction. How can the contract frontend app get the result of the transaction after returning from the wallet?
I've faced the same problem. For this moment near-api set transaction info in the browser url. So you get the transaction hash from url after returning from the wallet. Then using transaction hash get info about it using near-api-js:
const { providers } = require("near-api-js");
//network config (replace testnet with mainnet or betanet)
const provider = new providers.JsonRpcProvider(
"https://archival-rpc.testnet.near.org"
);
const TX_HASH = "9av2U6cova7LZPA9NPij6CTUrpBbgPG6LKVkyhcCqtk3";
// account ID associated with the transaction
const ACCOUNT_ID = "sender.testnet";
getState(TX_HASH, ACCOUNT_ID);
async function getState(txHash, accountId) {
const result = await provider.txStatus(txHash, accountId);
console.log("Result: ", result);
}
Documentation: https://docs.near.org/docs/api/naj-cookbook#get-transaction-status
There are 2 options:
Use provider.txStatus like Tom Links said. But the cons : we only know transaction success or fail but not the response from smart contract.
Seperate deposit api and actions api -> User must deposit before call actions api, so we can read the response.

Calling my .NET Core Teams Bot from Angular

I have created a Teams bot in .NET Core from following the sample found here: https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/57.teams-conversation-bot
This is working and is running locally with ngrok. I have a controller with a route of api/messages:
[Route("api/messages")]
[ApiController]
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter Adapter;
private readonly IBot Bot;
public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
{
Adapter = adapter;
Bot = bot;
}
[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await Adapter.ProcessAsync(Request, Response, Bot);
}
}
I now want to call a POST to api/messages from my Angular client using TypeScript to send a proactive message to a specific Teams user.
I did figure out how to set the ConversationParameters in TeamsConversationBot.cs to a specific Teams user by doing the following:
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new[] { new ChannelAccount("[insert unique Teams user guid here]") },
TenantId = turnContext.Activity.Conversation.TenantId,
};
but what I'm struggling with is how to build a JSON request that sends the Teams user guid (and maybe a couple other details) to my api/messages route from TypeScript.
How do I go about doing this? What parameters/body do I need to send? I haven't been able to find samples online that show how to do this.
Update below for added clarification
I am building a web chat app using Angular for our customers. What I'm trying to do is send a proactive message to our internal employees, who are using Microsoft Teams, when a customer performs some action via the chat app (initiates a conversation, sends a message, etc.).
I've built a Teams bot using .NET Core using this sample: https://kutt.it/ZCftjJ. Modifiying that sample, I can hardcode my Teams user ID and the proactive message is showing up successfully in Teams:
var proactiveMessage = MessageFactory.Text($"This is a proactive message.");
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new[] { new ChannelAccount("insert Teams ID here") },
TenantId = turnContext.Activity.Conversation.TenantId,
};
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(teamsChannelId, serviceUrl, credentials, conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(_appId, conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(proactiveMessage, c2);
},
cancellationToken);
},
cancellationToken);
What I'm struggling with is:
How to configure my Angular app to notify my bot of a new proactive message I want to send.
How to configure the bot to accept some custom parameters (Teams user ID, message).
It sounds like you've got some progress with pro-active messaging already. Is it working 100%? If not, I've covered the topic a few times here on stack overflow - here's an example that might help: Programmatically sending a message to a bot in Microsoft Teams
However, with regards -trigging- the pro-active message, the truth is you can do it from anywhere/in any way. For instance, I have Azure Functions that run on their own schedules, and pro-active send messages as if they're from the bot, even though the code isn't running inside the bot at all. You haven't fully described where the Angular app fits into the picture (like who's using it for what), but as an example in your scenario, you could create another endpoint inside your bot controller, and do the work inside there directly (e.g. add something like below:)
[HttpPost]
public async Task ProActiveMessage([FromQuery]string conversationId)
{
//retrieve conversation details by id from storage (e.g. database)
//send pro-active message
//respond with something back to the Angular client
}
hope that helps,
Hilton's answer is still good, but the part about proactively messaging them without prior interaction requires too long of a response. So, responding to your latest comments:
Yes, the bot needs to be installed for whatever team the user resides in that you want to proactively message. It won't have permissions to do so, otherwise.
You don't need to override OnMembersAddedAsync; just query the roster (see below).
You don't need a conversation ID to do this. I'd make your API, instead, accept their Teams ID. You can get this by querying the Teams Roster, which you'll need to do in advance and store in a hash table or something...maybe a database if your team size is sufficiently large.
As far as required information, you need enough to build the ConversationParameters:
var conversationParameters = new ConversationParameters
{
IsGroup = false,
Bot = turnContext.Activity.Recipient,
Members = new ChannelAccount[] { teamMember },
TenantId = turnContext.Activity.Conversation.TenantId,
};
...which you then use to CreateConversationAsync:
await ((BotFrameworkAdapter)turnContext.Adapter).CreateConversationAsync(
teamsChannelId,
serviceUrl,
credentials,
conversationParameters,
async (t1, c1) =>
{
conversationReference = t1.Activity.GetConversationReference();
await ((BotFrameworkAdapter)turnContext.Adapter).ContinueConversationAsync(
_appId,
conversationReference,
async (t2, c2) =>
{
await t2.SendActivityAsync(proactiveMessage, c2);
},
cancellationToken);
},
cancellationToken);
Yes, you can modify that sample. It returns a Bad Request because only a particular schema is allowed on /api/messages. You'll need to add your own endpoint. Here's an example of NotifyController, which one of our other samples uses. You can see that it accepts GET requests. You'd just need to modify that our build your own that accepts POST requests.
All of this being said, all of this seems like it may be a bigger task than you're ready for. Nothing wrong with that; that's how we learn. Instead of jumping straight into this, I'd start with:
Get the Proactive Sample working and dig through the code until you really understand how the API part works.
Get the Teams Sample working, then try to make it message individual users.
Then build your bot that messages users without prior interaction.
If you run into trouble feel free to browse my answers. I've answered similar questions to this, a lot. Be aware, however, that we've switched from the Teams Middleware that I mention in some of my answers to something more integrated into the SDK. Our Teams Samples (samples 50-60) show how to do just about everything.

Spring Stomp #SubscribeMapping for User Destination

I am trying to get notified when an user subscribes to a stomp user destination using #SubscribeMapping annotation. My ideia is to send some initialization data to when it joins.
Although i am not being able to get this working:
Javascript:
stompClient.subscribe('/user/monitor', function(msg) {
console.log(msg);
});
Java side:
#SubscribeMapping("/monitor-user{clientId}")
public void init(#DestinationVariable("clientId") String clientId) {
messagingTemplate.convertAndSend("/user/" + clientId + "/monitor", getOps());
}
I tried many mapping combinations such as "/user/monitor-{clientId}", "/user/monitor" (Removing clientId), no success at all.
What is the expected mapping value so this get called?
Thank you!
Since the client subscribes to the "/user/monitor" queue, the server #SubscribeMapping annotation must be to "/user/monitor" and not to "/user/monitor{something}".
The server can understand what client is depending on your authentication scheme. If you use a sockjs websocket you can use HTTP authentication, and hence leverage Spring Security, and add a "Principal" parameter to your function that will hold user information:
#SubscribeMapping("/monitor")
public void init(Principal p) {
String user = p.getName();
messagingTemplate.convertAndSendToUser(user, "/monitor", getOps());
}
If you don't use http authentication you may send the server the user info, for example adding a custom STOMP header that can be accessed from the server using a SimpMessageHeaderAccessor:
#SubscribeMapping("/monitor")
public void init(SimpMessageHeaderAccessor accessor) {
String user = accessor.getFirstNativeHeader("userid");
messagingTemplate.convertAndSendToUser(user, "/monitor", getOps());
}
Another way could be to subscribe to a different queue name which contains the user information (and this maybe was your proposal). The client must use a code similar to:
stompClient.subscribe('/topic/byUser/'+userId, function(msg) {
console.log(msg);
});
so that the server can access it this way:
#SubscribeMapping("/byUser/{userId}")
public void init(#DestinationVariable String userId) {
messagingTemplate.convertAndSendToUser(userId, "/monitor", getOps());
}
but in this case keep in mind that that queue is public and hence other clients can access their messages if they knows other client names.

spring websocket notification

Ok here i'm , i'm right now following the guides on spring site but i'm having problem on how to deliver a notification to only one user using WebSocket, i'm following this guide https://spring.io/guides/gs/messaging-stomp-websocket/ .
What I want is: i have 2 users, both of them subscribe to process1... User1 need to let the server process his pass...now i want that the server will deliver the notification only to User1...
#Controller
public class ProcessController {
#MessageMapping("/ProcessOwner/approve/{pass}")
#SendTo("")
public String notifica(#DestinationVariable String pass)throws Exception{
return "ok"+pass;
}
}
Now what should i write in the #SendTo field to deliver the answer only to user1 ? if ill write /Process/process1 both user1 and user2 will receive the message....
You ca use the sufix. It's send to every unique client.
When you subscribe in the js code:
client.connect('guest', 'guest', function(frame) {
var suffix = frame.headers['queue-suffix'];
client.subscribe("/queue/error" + suffix, function(msg) {
// handle error
});
client.subscribe("/queue/position-updates" + suffix, function(msg) {
// handle position update
});
});
On the server side you can use #ReplyToUser or the message template
String user = "fabrice";
String queue = "/queue/position-updates";
this.messagingTemplate.convertAndSendToUser(user, queue, position);
See more here: http://assets.spring.io/wp/WebSocketBlogPost.html (section: Sending Messages To a Single User)

Resources