Publish events with MassTransit - masstransit

I'm trying to publish message in one microservice and get it in another one, but cannot implement this using MassTransit 5.5.3 with RabbitMQ.
As far as I know we don't have to create a ReceiveEndpoint to be able to publish event so I'm just creating the same message interface in both services and publish a message, but as I can see in RabbitMQ it either goes to nowhere (if doesn't mapped to a queue) or goes to "_skipped" queue.
Publisher:
namespace Publisher
{
class Program
{
static async Task Main(string[] args)
{
var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host("host", "vhost", h =>
{
h.Username("xxx");
h.Password("yyy");
});
});
bus.Start();
await bus.Publish<Message>(new { Text = "Hello World" });
Console.ReadKey();
bus.Stop();
}
}
public interface Message
{
string Text { get; set; }
}
}
Consumer:
namespace Consumer
{
class Program
{
static async Task Main(string[] args)
{
var bus = Bus.Factory.CreateUsingRabbitMq(cfg =>
{
IRabbitMqHost host = cfg.Host("host", "vhost", h =>
{
h.Username("xxx");
h.Password("yyy");
});
cfg.ReceiveEndpoint(host, e =>
{
e.Consumer<MbConsumer>();
});
});
bus.Start();
bool finish = false;
while(!finish)
{
await Task.Delay(1000);
}
bus.Stop();
}
}
public interface Message
{
string Text { get; set; }
}
public class MbConsumer : IConsumer<Message>
{
public async Task Consume(ConsumeContext<Message> context)
{
await Console.Out.WriteLineAsync(context.Message.Text);
}
}
}
I'm expeting the consumer to get the message once it's been published but it doesn't get it. I think this is because the full message types are different ("Publisher.Message" vs. "Consumer.Message") so message contract is different. How should I fix this code to get the event in the consumer? Looks like I'm missing some fundamental thing about RabbitMQ or MassTransit.

Your guess is correct. MassTransit uses the fully qualified class name as the message contract name. MassTransit also uses type-based routing, so FQCNs are used to create exchanges and bindings.
So, if you move your message class to a separate namespace like:
namespace Messages
{
public interface Message
{
string Text { get; set; }
}
}
You then can reference this type when you publish a message
await bus.Publish<Messages.Message>(new { Text = "Hello World" });
and define your consumer
public class MbConsumer : IConsumer<Messages.Message>
{
public async Task Consume(ConsumeContext<Message> context)
{
await Console.Out.WriteLineAsync(context.Message.Text);
}
}
it will work.
You might want also to look at RMQ management UI to find out about MassTransit topology. With your code, you will see two exchanges, one Publisher.Message and another one Consumer.Message where your consumer queue is bound to the Consumer.Message exchange, but you publish messages to the Publisher.Message exchange and they just vanish.
I would also suggest specifying a meaningful endpoint name for your receive endpoint:
cfg.ReceiveEndpoint(host, "MyConsumer", e =>
{
e.Consumer<MbConsumer>();
});

Related

Asp.Net Core MVC - Mailjet is not sending emails

I am using mailjet email service in my website to send emails. I have made sure that my SPF and DKIM records are up to date with my domain. However, when I run the project and register the user, it suppose to send email to the user's email id. It doesn't show any type of error but simply doesn't send the email. I have tried multiple times to change my code but still I am not able to send emails. Is there something I am missing in this context?
here is the code for the EmailSender.cs file :
using Mailjet.Client;
using Mailjet.Client.Resources;
using Microsoft.AspNetCore.Identity.UI.Services;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Derawala.Models
{
public class EmailSender : IEmailSender
{
public Task SendEmailAsync(string email, string subject, string htmlMessage)
{
return Execute(email, subject, htmlMessage);
}
public async Task Execute(string email, string subject, string body)
{
MailjetClient client = new MailjetClient("******************", "*****************");
MailjetRequest request = new MailjetRequest
{
Resource = Send.Resource,
}
.Property(Send.Messages, new JArray {
new JObject {
{
"From",
new JObject {
{"Email", "*********************"},
{"Name", "Derawala Education & Charitable Trust"}
}
}, {
"To",
new JArray {
new JObject {
{
"Email",
email
}, {
"Name",
"Derawala Trust"
}
}
}
}, {
"Subject",
subject
},
{
"HTMLPart",
body
},
}
});
await client.PostAsync(request);
}
}
}
P.S. - I have hidden public key and secret key for this question only, in program I am using the real ones.
In above image, we can see that SPF and DKIM are up to date with the domain.

Send message only to certain client using websockets with Rsocket and Spring Webflux

I am trying to use Rsocket with websocket in one of my POC projects. In my case user login is not required. I would like to send a message only to certain clients when I receive a message from another service. Basically, my flow goes like this.
Service A Service B
|--------| websocket |------------------| Queue based comm |---------------|
| Web |----------------->| Rsocket server |--------------------->| Another |
| |<-----------------| using Websocket |<---------------------| service |
|--------| websocket |------------------| Queue based comm |---------------|
In my case, I am thinking to use a unique id for each connection and each request. Merge both identifiers as correlation id and send the message to Service B and when I get the message from Service B figure which client it needs to go to and send it. Now I understand I may not need 2 services to do this but I am doing this for a few other reasons. Though I have a rough idea about how to implement other pieces. I am new to the Rsocket concept. Is it possible to send a message to the only certain client by a certain id using Spring Boot Webflux, Rsocket, and websocket?
Basically, I think you have got two options here. The first one is to filter the Flux which comes from Service B, the second one is to use RSocketRequester and Map as #NikolaB described.
First option:
data class News(val category: String, val news: String)
data class PrivateNews(val destination: String, val news: News)
class NewsProvider {
private val duration: Long = 250
private val externalNewsProcessor = DirectProcessor.create<News>().serialize()
private val sink = externalNewsProcessor.sink()
fun allNews(): Flux<News> {
return Flux
.merge(
carNews(), bikeNews(), cosmeticsNews(),
externalNewsProcessor)
.delayElements(Duration.ofMillis(duration))
}
fun externalNews(): Flux<News> {
return externalNewsProcessor;
}
fun addExternalNews(news: News) {
sink.next(news);
}
fun carNews(): Flux<News> {
return Flux
.just("new lambo!!", "amazing ferrari!", "great porsche", "very cool audi RS4 Avant", "Tesla i smarter than you")
.map { News("CAR", it) }
.delayElements(Duration.ofMillis(duration))
.log()
}
fun bikeNews(): Flux<News> {
return Flux
.just("specialized enduro still the biggest dream", "giant anthem fast as hell", "gravel long distance test")
.map { News("BIKE", it) }
.delayElements(Duration.ofMillis(duration))
.log()
}
fun cosmeticsNews(): Flux<News> {
return Flux
.just("nivea - no one wants to hear about that", "rexona anti-odor test")
.map { News("COSMETICS", it) }
.delayElements(Duration.ofMillis(duration))
.log()
}
}
#RestController
#RequestMapping("/sse")
#CrossOrigin("*")
class NewsRestController() {
private val log = LoggerFactory.getLogger(NewsRestController::class.java)
val newsProvider = NewsProvider()
#GetMapping(value = ["/news/{category}"], produces = [MediaType.TEXT_EVENT_STREAM_VALUE])
fun allNewsByCategory(#PathVariable category: String): Flux<News> {
log.info("hello, getting all news by category: {}!", category)
return newsProvider
.allNews()
.filter { it.category == category }
}
}
The NewsProvider class is a simulation of your Service B, which should return Flux<>. Whenever you call the addExternalNews it's going to push the News returned by the allNews method. In the NewsRestController class, we filter the news by category. Open the browser on localhost:8080/sse/news/CAR to see only car news.
If you want to use RSocket instead, you can use a method like this:
#MessageMapping("news.{category}")
fun allNewsByCategory(#DestinationVariable category: String): Flux<News> {
log.info("RSocket, getting all news by category: {}!", category)
return newsProvider
.allNews()
.filter { it.category == category }
}
Second option:
Let's store the RSocketRequester in the HashMap (I use vavr.io) with #ConnectMapping.
#Controller
class RSocketConnectionController {
private val log = LoggerFactory.getLogger(RSocketConnectionController::class.java)
private var requesterMap: Map<String, RSocketRequester> = HashMap.empty()
#Synchronized
private fun getRequesterMap(): Map<String, RSocketRequester> {
return requesterMap
}
#Synchronized
private fun addRequester(rSocketRequester: RSocketRequester, clientId: String) {
log.info("adding requester {}", clientId)
requesterMap = requesterMap.put(clientId, rSocketRequester)
}
#Synchronized
private fun removeRequester(clientId: String) {
log.info("removing requester {}", clientId)
requesterMap = requesterMap.remove(clientId)
}
#ConnectMapping("client-id")
fun onConnect(rSocketRequester: RSocketRequester, clientId: String) {
val clientIdFixed = clientId.replace("\"", "") //check serialezer why the add " to strings
// rSocketRequester.rsocket().dispose() //to reject connection
rSocketRequester
.rsocket()
.onClose()
.subscribe(null, null, {
log.info("{} just disconnected", clientIdFixed)
removeRequester(clientIdFixed)
})
addRequester(rSocketRequester, clientIdFixed)
}
#MessageMapping("private.news")
fun privateNews(news: PrivateNews, rSocketRequesterParam: RSocketRequester) {
getRequesterMap()
.filterKeys { key -> checkDestination(news, key) }
.values()
.forEach { requester -> sendMessage(requester, news) }
}
private fun sendMessage(requester: RSocketRequester, news: PrivateNews) {
requester
.route("news.${news.news.category}")
.data(news.news)
.send()
.subscribe()
}
private fun checkDestination(news: PrivateNews, key: String): Boolean {
val list = destinations(news)
return list.contains(key)
}
private fun destinations(news: PrivateNews): List<String> {
return news.destination
.split(",")
.map { it.trim() }
}
}
Note that we have to add two things in the rsocket-js client: a payload in SETUP frame to provide client-id and register the Responder, to handle messages sent by RSocketRequester.
const client = new RSocketClient({
// send/receive JSON objects instead of strings/buffers
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
//for connection mapping on server
payload: {
data: "provide-unique-client-id-here",
metadata: String.fromCharCode("client-id".length) + "client-id"
},
// ms btw sending keepalive to server
keepAlive: 60000,
// ms timeout if no keepalive response
lifetime: 180000,
// format of `data`
dataMimeType: "application/json",
// format of `metadata`
metadataMimeType: "message/x.rsocket.routing.v0"
},
responder: responder,
transport
});
For more information about that please see this question: How to handle message sent from server to client with RSocket?
I didn't yet personally use RSocket with WebSocket transport, but as stated in RSocket specification underlying transport protocol shouldn't even be important.
One RSocket component is at the same time server and a client. So when browsers connect to your RSocket "server" you can inject the RSocketRequester instance which you can then use to send messages to the "client".
You can then add these instances in your local cache (e.g. put them in some globally available ConcurrentHashMap with key of your choosing - something from which you'll know/be able to calculate to which clients should the message from Service B be propagated).
Then in the code where you receive message from Service B just fetch all RSocketRequester instances from the local cache which match your criteria and send them the message.

HotChocolate (GraphQL) schema first approach on complex type

I'm novice in HotChocolate and I'm trying to PoC some simple usage.
I've created very simple .graphql file:
#camera.graphql
type Camera {
id: ID!
name: String!
}
type Query {
getCamera: Camera!
}
And a very simple .NET code for camera wrapping:
public class QlCamera
{
public static QlCamera New()
{
return new QlCamera
{
Id = Guid.NewGuid().ToString(),
Name = Guid.NewGuid().ToString()
};
}
public string Id { get; set; }
public string Name { get; set; }
}
as well as such for schema creation:
public void CreateSchema()
{
string path = System.IO.Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
var smBuilder = SchemaBuilder.New();
smBuilder.AddDocumentFromFile(path + "/GraphQL/camera.graphql");
smBuilder.AddResolver("Query", "getCamera", () => QlCamera.New());
var schema = smBuilder.Create();
}
On the last line however I do get an exception :
HotChocolate.SchemaException: 'Multiple schema errors occured:
The field Camera.id has no resolver. - Type: Camera
The field Camera.name has no resolver. - Type: Camera
'
I've tried to create :
public class QlCameraType : ObjectType<QlCamera>
{
protected override void Configure(IObjectTypeDescriptor<QlCamera> descriptor)
{
descriptor.Name("Camera");
descriptor.Field(t => t.Id).Type<NonNullType<StringType>>();
descriptor.Field(t => t.Name).Type<StringType>();
}
}
and to replace
smBuilder.AddResolver("Query", "getCamera", () => QlCamera.New());
with
smBuilder.AddResolver("Query", "getCamera", () => new QlCameraType());
But I continue to get the same exception.
Obviously I miss something here, But I cannot understand what exactly.
Could someone explain me what I do miss ?
(I've passed few times trough the documentation, but I cannot find relevant help there)
As exception clearly states - there are no revolvers bind for the particular fields ("id" and "name") of the "Camera" type/object.
So they just have to be added with :
smBuilder.AddResolver("Camera", "id", rc => rc.Parent<QlCamera>().Id);
smBuilder.AddResolver("Camera", "name", rc => rc.Parent<QlCamera>().Name);
And that is it.

Emit deprecation warnings with Apollo client

Background
We are working on a fairly large Apollo project. A very simplified version of our api looks like this:
type Operation {
foo: String
activity: Activity
}
type Activity {
bar: String
# Lots of fields here ...
}
We've realised splitting Operation and Activity does no benefit and adds complexity. We'd like to merge them. But there's a lot of queries that assume this structure in the code base. In order to make the transition gradual we add #deprecated directives:
type Operation {
foo: String
bar: String
activity: Activity #deprecated
}
type Activity {
bar: String #deprecated(reason: "Use Operation.bar instead")
# Lots of fields here ...
}
Actual question
Is there some way to highlight those deprecations going forward? Preferably by printing a warning in the browser console when (in the test environment) running a query that uses a deprecated field?
So coming back to GraphQL two years later I just found out that schema directives can be customized (nowadays?). So here's a solution:
import { SchemaDirectiveVisitor } from "graphql-tools"
import { defaultFieldResolver } from "graphql"
import { ApolloServer } from "apollo-server"
class DeprecatedDirective extends SchemaDirectiveVisitor {
public visitFieldDefinition(field ) {
field.isDeprecated = true
field.deprecationReason = this.args.reason
const { resolve = defaultFieldResolver, } = field
field.resolve = async function (...args) {
const [_,__,___,info,] = args
const { operation, } = info
const queryName = operation.name.value
// eslint-disable-next-line no-console
console.warn(
`Deprecation Warning:
Query [${queryName}] used field [${field.name}]
Deprecation reason: [${field.deprecationReason}]`)
return resolve.apply(this, args)
}
}
public visitEnumValue(value) {
value.isDeprecated = true
value.deprecationReason = this.args.reason
}
}
new ApolloServer({
typeDefs,
resolvers,
schemaDirectives: {
deprecated: DeprecatedDirective,
},
}).listen().then(({ url, }) => {
console.log(`🚀 Server ready at ${url}`)
})
This works on the server instead of the client. It should print all the info needed to track down the faulty query on the client though. And having it in the server logs seem preferable from a maintenance perspective.

Email Validation in a controller Grails

I'm just trying to validate an email address in a Controller which I thought would be simple. The method I've done is as follows:
def emailValidCheck(String emailAddress) {
EmailValidator emailValidator = EmailValidator.getInstance()
if (!emailAddress.isAllWhitespace() || emailAddress!=null) {
String[] email = emailAddress.replaceAll("//s+","").split(",")
email.each {
if (emailValidator.isValid(it)) {
return true
}else {return false}
}
}
}
This is being used with a sendMail function, which my code for that is here:
def emailTheAttendees(String email) {
def user = lookupPerson()
if (!email.isEmpty()) {
def splitEmails = email.replaceAll("//s+","").split(",")
splitEmails.each {
def String currentEmail = it
sendMail {
to currentEmail
System.out.println("what's in to address:"+ currentEmail)
subject "Your Friend ${user.username} has invited you as a task attendee"
html g.render(template:"/emails/Attendees")
}
}
}
}
This works and sends emails to valid email addresses, but if I put in something random that is not an address just breaks with sendMail exception. I can't understand why it's not validating correctly and even going into the emailTheAttendees() method ... which is being called in the save method.
I would suggest using constraints and a command object to achieve this. Example:
Command Object:
#grails.validation.Validateable
class YourCommand {
String email
String otherStuffYouWantToValidate
static constraints = {
email(blank: false, email: true)
...
}
}
Call it like this in your controller:
class YourController {
def yourAction(YourCommand command) {
if (command.hasErrors()) {
// handle errors
return
}
// work with the command object data
}
}

Resources