Using grpc as client in webflux - spring-webflux

Our team's project is a product that accepts requests and then forwards them to processing business machines. We use Springboot's webflux as a framework and use gRPC in it as a client to send requests to business machines.
Its process is as follows:
User <-> Webflux <-> gRPC <-> The machine that actually handle the business
We were drawn to the asynchronous non-blocking nature of webflux and gRPC. gRPC uses streaming.
The controller and service we wrote are roughly as follows:
#PostMapping(”xxxx“)
pulbic Mono<String> mac() {
final COmpletableFuture<String> future = new CompletableFuture<>();
final StreamObserver<MacMessage> request = gRPCService.getStub.mac(new StreamObserver<>() {
String mac;
#Override
public void onNext(MacMessage value) {
mac = Base64.encode(value.getMac.toByteArray());
}
#Override
public void onError(Throwable t) {
future.completeExceptionally(t);
}
#Override
public void onCompleted() {
future.complete(mac);
}
});
request.onNext(MacMessage.newBuilder().setxxxx....build());
request.onCompleted();
return Mono.fromFuture(future);
}
In addition, when I tried the Mono API recently, I found another way of writing, which does not require the use of CompletableFuture, and the effect is similar:
#PostMapping(”xxxx“)
pulbic Mono<String> mac() {
return Mono.create(monoSink -> {
final StreamObserver<MacMessage> request = gRPCService.getStub.mac(new StreamObserver<>() {
String mac;
#Override
public void onNext(MacMessage value) {
mac = Base64.encode(value.getMac.toByteArray());
}
#Override
public void onError(Throwable t) {
monoSink.error(t);
}
#Override
public void onCompleted() {
monoSink.success(mac);
}
});
request.onNext(MacMessage.newBuilder().setxxxx....build());
request.onCompleted();
});
}
I would like to know whether the above writing methods have met the requirements of asynchronous non-blocking, and if not, how should they be used?
The reason for this doubt is that (taking the above code as an example) we found that if we only use printing to consume the response value in gRPC StreamObserver#onNext, the performance is much higher than the above two writing methods, and the performance of the above two writing methods is similar . This makes me wonder, is there such a big difference in the performance of printing the consumption response value and returning the response value?

Related

Spring webflux filter: How to get the reactor context after the query execution?

Spring boot 2.1.5
Project Reactor 3.2.9
In my webflux project I extensively use the reactor contexts in order to pass around some values.
I set up a filter and am trying to log things which are in the context and to log different things in case of error/success.
I have checked this documentation: https://projectreactor.io/docs/core/release/reference/#context
I still struggle (especially on the error side) to get it.
Basically, I have this filter:
#Component
public class MdcWebFilter implements WebFilter {
#NotNull
#Override
public Mono<Void> filter(#NotNull ServerWebExchange serverWebExchange,
WebFilterChain webFilterChain) {
Mono<Void> filter = webFilterChain.filter(serverWebExchange);
return filter
.doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
#Override
public void accept(Void aVoid, Throwable throwable) {
//Here i would like to be able to access to the request's context
System.out.println("doAfterSuccessOrError:" + (throwable==null ? "OK" : throwable.getMessage())+"log the context");
}
})
.doOnEach(new Consumer<Signal<Void>>() {
#Override
public void accept(Signal<Void> voidSignal) {
//Here i have the context but i don't really know if i am in success or error
System.out.println("doOnEach:"+"Log OK/KO and the exception" + voidSignal.getContext());
}
})
.subscriberContext(context -> context.put("somevar", "whatever"));
}
}
I also tried with a flatMap() and a Mono.subscriberContext() but i am not sure how to plug correctly with the filter (especially in error).
What would be the best way to achieve this ?
I'm not sure whether it possible access request reactor context from within WebFilter. WebFilter context exists in another Mono chain.
But it is do possible to assosiate attributes with request and able to fetch these attributes during request life time RequestContextHolder for Reactive Web
Very similar to Servlet API.
Controller:
#GetMapping(path = "/v1/customers/{customerId}")
public Mono<Customer> getCustomerById(
#PathVariable("customerId") String customerId,
ServerWebExchange serverWebExchange)
{
serverWebExchange.getAttributes().put("traceId", "your_trace_id");
return customerService.findById(customerId);
}
WebFilter:
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// ...
String traceId = exchange.getAttributeOrDefault("traceId", "default_value_goes_here");
//...
return chain.filter(exchange);
}
I know this is probably not the cleanest of the solutions, but you could create a container class that would keep the context between your two callbacks.
You would store the context at doOnEach and then you would be able to load it back at doAfterSuccessOrError:
public Mono<Void> filter(#NotNull ServerWebExchange serverWebExchange, WebFilterChain webFilterChain) {
#lombok.Data
class MyContextContainer {
private Context context;
}
MyContextContainer container = new MyContextContainer();
Mono<Void> filter = webFilterChain.filter(serverWebExchange);
return filter
.doAfterSuccessOrError(new BiConsumer<Void, Throwable>() {
#Override
public void accept(Void aVoid, Throwable throwable) {
// load the context here
Context context = container.getContext();
// then do your stuff here
}
})
.doOnEach(new Consumer<Signal<Void>>() {
#Override
public void accept(Signal<Void> voidSignal) {
// store the context here
container.setContext(voidSignal.getContext());
}
})
.subscriberContext(context -> context.put("somevar", "whatever"));
}
It doesn't need to be a class, really. It could be an AtomicReference, but you get the idea.
Again, this might be just a workaround. I believe there must be a better way to access the context.

How to send constantly updates using .Net Core SignalR?

I am new to SignalR and I would like to build such app -- every second a hub sends current time to all connected clients.
I found tutorial, but it is for .Net Framework (not Core): https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/tutorial-high-frequency-realtime-with-signalr So on one hand I don't know how to translate it to .Net Core SignalR, on the other hand I don't know how to write it from scratch (the limiting condition is the fact a hub is a volatile entity, so I cannot have state in it).
I need something static (I guess) with state -- let's say Broadcaster, when I create some cyclic action which in turn will send updates to clients. If such approach is OK, how to initialize this Broadcaster?
Currently I added such static class:
public static class CrazyBroadcaster
{
public static void Initialize(IServiceProvider serviceProvider)
{
var scope = serviceProvider.CreateScope();
var hub = scope.ServiceProvider.GetRequiredService<IHubContext<ChatHub>>();
var sub = Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe(_ => hub.Clients.All.SendAsync("Bar", DateTimeOffset.UtcNow));
}
}
Yes, I know it is leaky. I call this method at the end of Startup.Configure, probably tons of violations here, but so far it is my best shot.
The missing piece was hosted service, i.e. the code that runs in the background -- https://learn.microsoft.com/en-US/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2.
So my crazy class is now transformed into:
public sealed class HostedBroadcaster : IHostedService, IDisposable
{
private readonly IHubContext<ChatHub> hubContext;
private IDisposable subscription;
public HostedBroadcaster(IHubContext<ChatHub> hubContext)
{
this.hubContext = hubContext;
}
public void Dispose()
{
this.subscription?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
this.subscription = Observable.Interval(TimeSpan.FromSeconds(1)).Subscribe(_ => hubContext.Clients.All.SendAsync("Bar", DateTimeOffset.UtcNow));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
this.subscription?.Dispose();
return Task.CompletedTask;
}
}

Spring Data Rest ID conversion using HashIDs

We have a concern exposing internal IDs to the outside world. Therefore I'm thinking about using a hashing mechanism (current choice is hashids) to hash our IDs.
I tried to use a #JsonSerializer and #JsonDeserializer mapping on the Entities ID field. But this only takes effect when including the ID in the body, and has no impact on the IDs in the URL paths.
Is there a possibility to do this, e.g. something like an ID Translation SPI?
The only thing I can think of is to create a request filter that would take the request with encoded ID in URL, then decode the ID and redirect to an URL with decoded ID.
What you need is working "right from the box" in Spring Data REST by customizing item resource URIs:
#Configuration
public class RestConfigurer extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.withEntityLookup().forRepository(ModelRepo.class, model -> HashIdUtil.encode(model.getId()), ModelRepo::findByEncodedId);
super.configureRepositoryRestConfiguration(config);
}
}
public interface ModelRepo extends JpaRepository<Model, Long> {
default Model findByEncodedId(String encodedId) {
return getById(HashIdUtil.decode(encodedId));
}
Model getById(Long id);
}
public class HashIdUtil {
private static final Hashids HASHIDS = new Hashids("salt", 8);
public static String encode(Long source) {
return HASHIDS.encode(source);
}
public static Long decode(String source) {
return HASHIDS.decode(source)[0];
}
}
Unfortunately, due to the bug (I suppose), PUT/PATCH-ing entities does not work in Spring Boot 2+, unlike the previous version of SB (1.5+) where it works as expected.
See my demo: sdr-hashids-demo
You could try using a converter.
#Component
#AllArgsConstructor
public class HashIdConverter implements Converter<String, Long> {
private final HashidsUtil hashidsUtil;
#Override
public Long convert(#NonNull String source) {
return hashidsUtil.decodeId(source);
}
}
Using it the way I just showed you is a bit unsafe, but it can do the work quite well if you are careful enough

ASP.NET MVC 4 Background operations

I am looking for nice and elegant architectural solution for ASP.NET MVC 4 Background operations.
My goal is developing some world that lives its own life and clients can only interact with it. For now let's say that it will be a simple clock and clients can watсh on it.
Now I have WebBackrounder + SignalR packages.
WebBackrounder:
[assembly: PostApplicationStartMethod(typeof(WebBackgrounderSetup), "Start")]
[assembly: ApplicationShutdownMethod(typeof(WebBackgrounderSetup), "Shutdown")]
namespace LibcanvasStudy.App_Start
{
public static class WebBackgrounderSetup
{
static readonly JobManager _jobManager = CreateJobWorkersManager();
public static RedrawJob RedrawJob { get; private set; }
public static void Start()
{
_jobManager.Start();
}
public static void Stop()
{
_jobManager.Stop();
}
public static void Shutdown()
{
_jobManager.Dispose();
}
private static JobManager CreateJobWorkersManager()
{
RedrawJob = new RedrawJob(TimeSpan.FromSeconds(1));
var manager = new JobManager(new[] { RedrawJob }, new JobHost());
return manager;
}
}
RedrawJob while its execution rise event and SignalR hub catches it:
public class CanvasHub : Hub
{
public CanvasHub()
{
if (WebBackgrounderSetup.RedrawJob != null)
WebBackgrounderSetup.RedrawJob.Executing += (sender, args) => Request(args);
}
public void Request(RedrawEventArgs eventArgs)
{
Clients.All.redraw(...);
}
}
I have one main problem for now - How I can dynamicaly add and remove jobs from my JobManager?
Also I don't like this job-event system, it's a little bit awkwardly for me. Any proposal?
What darin says is correct, but it can be worked around. For example what I do for these kind of scenarios is to have a internal WCF service that handles all jobs, you call it from the schedular or workflow engine using net.tcp or memory pipe. This way you benefit from all IIS sugar coating. And dont need to marshal your threads and error handing.
Second, implement some kind of event bus to decouple SignalR and your domain logic.
I have written this little lib that proxies between domain events and SignalR
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy
Install using nuget
Install-Package SignalR.EventAggregatorProxy
Please look at the wiki for the few easy steps required to hook it up
https://github.com/AndersMalmgren/SignalR.EventAggregatorProxy/wiki
For a super lightweight in process event bus look at Caliburn.Micros EventAggregator
http://nuget.org/packages/Caliburn.Micro.EventAggregator/
What do you think about using this code in any part of MVC app?
IHubContext hub = GlobalHost.ConnectionManager.GetHubContext<CanvasHub>();
hub.Clients.All.redraw(redrawData);

ChannelFactory: creating and disposing

I have written an Sdk that is used by a WPF client, and takes care of calling WCF services and caching. These WCF services are called using the ChannelFactory, so I don't have service references. To do that, I created a factory that handles opening and closing ChannelFactory and ClientChannel as follows:
public class ProjectStudioServiceFactory : IDisposable
{
private IProjectStudioService _projectStudioService;
private static ChannelFactory<IProjectStudioService> _channelFactory;
public IProjectStudioService Instance
{
get
{
if (_channelFactory==null) _channelFactory = new ChannelFactory<IProjectStudioService>("ProjectStudioServiceEndPoint");
_projectStudioService = _channelFactory.CreateChannel();
((IClientChannel)_projectStudioService).Open();
return _projectStudioService;
}
}
public void Dispose()
{
((IClientChannel)_projectStudioService).Close();
_channelFactory.Close();
}
}
And each request I call like:
using (var projectStudioService = new ProjectStudioServiceFactory())
{
return projectStudioService.Instance.FindAllCities(new FindAllCitiesRequest()).Cities;
}
Although this works, it's slow because for every request the client channel and factory is opened and closed. If I keep it open, it's very fast. But I was wondering what the best practise would be? Should I keep it open? Or not? How to handle this in a correct way?
Thanks Daniel, didn't see that post. So I guess that the following may be a good approach:
public class ProjectStudioServiceFactory : IDisposable
{
private static IProjectStudioService _projectStudioService;
private static ChannelFactory<IProjectStudioService> _channelFactory;
public IProjectStudioService Instance
{
get
{
if (_projectStudioService == null)
{
_channelFactory = new ChannelFactory<IProjectStudioService>("ProjectStudioServiceEndPoint");
_projectStudioService = _channelFactory.CreateChannel();
((IClientChannel)_projectStudioService).Open();
}
return _projectStudioService;
}
}
public void Dispose()
{
//((IClientChannel)_projectStudioService).Close();
//_channelFactory.Close();
}
}