How to create dependency injection for ISendEndPointProvider for MultiBus - masstransit

I currently register Masstransit for two bus controls. I would like to inject ISendEndPointProvider for SecondBus as DI (I don't want to call GetSendEndPoint() in caller). How can I do that. When ISendEndPointProvider always return for the first bus.
Program.cs
services.AddMassTransit(x =>
{
x.AddConsumer<FirstConsumer>();
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(new Uri(rabbitMqOption.RabbitMqHost), h =>
{
h.Username(rabbitMqOption.RabbitMqUser);
h.Password(rabbitMqOption.RabbitMqPassword);
h.Heartbeat(10);
});
cfg.ReceiveEndpoint(rabbitMqOption.RabbitMqQueue, e =>
{
e.PrefetchCount = rabbitMqOption.RabbitMqPrefetchCount;
e.Consumer<FirstConsumer>(provider.Container);
});
}));
});
services.AddMassTransit<ISecondBus, SecondBus>(x =>
{
x.AddBus(provider => Bus.Factory.CreateUsingRabbitMq(cfg =>
{
cfg.Host(rabbitMqOption.NotificationRabbitMqHost, hostConfig =>
{
hostConfig.Username(rabbitMqOption.RabbitMqUser);
hostConfig.Password(rabbitMqOption.RabbitMqPassword);
});
}));
});

When using MultiBus, the ISendEndpointProvider and IPublishEndpoint interfaces when outside of a consumer will always point to IBus. There is no way to get the second bus without depending upon ISecondBus.
If the interface is used by a consumer (or a scoped consumer dependency), those interfaces will refer to the bus on which the message was received.
Configuration Errors
Also, you should change your .AddBus calls to .UsingRabbitMq((context, cfg), which gives you proper access to the container via the context argument. The AddBus syntax will eventually be deprecated.
The correct syntax is shown below (along with a corrected consumer configuration):
services.AddMassTransit(x =>
{
x.AddConsumer<FirstConsumer>();
x.UsingRabbitMq((context, cfg) =>
{
cfg.Host(new Uri(rabbitMqOption.RabbitMqHost), h =>
{
h.Username(rabbitMqOption.RabbitMqUser);
h.Password(rabbitMqOption.RabbitMqPassword);
h.Heartbeat(10);
});
cfg.ReceiveEndpoint(rabbitMqOption.RabbitMqQueue, e =>
{
e.PrefetchCount = rabbitMqOption.RabbitMqPrefetchCount;
e.ConfigureConsumer<FirstConsumer>(context);
});
}));
});

Adding a bit more for a solution that worked on my end after a couple hours of trial and error.
First, add this Nuget package: MassTransit.Extensions.DependencyInjection,
Then, here is exactly how I'm registering MassTransit:
services.AddMassTransit(x =>
{
x.AddConsumer(typeof(MyConsumerType));
x.UsingAzureServiceBus((context, cfg) =>
{
cfg.Host(configuration[Constants.KeyVaultSecretServiceBusConnectionString]);
cfg.Message<IDataPrivacyEvent>(cfg => cfg.SetEntityName(MessagingConstants.DataPrivacyEndpoint));
cfg.ConfigureEndpoints(context);
cfg.UseMessageRetry(m => m.Interval(5, TimeSpan.FromSeconds(10)));
cfg.SubscriptionEndpoint<IValidationEvent>(MessagingConstants.ValidationEndpoint, e =>
{
e.ConfigureConsumer(context, typeof(MyConsumerType));
e.UseMessageRetry(m => m.Interval(5, TimeSpan.FromSeconds(10)));
});
});
});

Related

Cypress test: Writing multiple elements to database failed

Trying to write multiple elements into database failed, only the last one is written:
// my_test.cy.js
import CreateProductPage from "../pages/CreateProductPage";
describe('product detail page', () => {
beforeEach(() => {
cy.login('admin', 'shop')
})
it('should print typed product', () => {
cy.createProduct(null, 'Component Product 0', '0')
CreateProductPage.elements.productDetailTabs().should('exist') // <--- to detect that the entity is written
cy.createProduct('combinable', 'Combined Test Product', '0')
CreateProductPage.elements.productDetailTabs().should('exist') // <--- to detect that the entity is written
})
})
// commands.js
Cypress.Commands.add('createProduct', (type, name, grossPrice) => {
cy.visit('/#/sw/product/create')
CreateProductPage.elements.productDetailTabs().should('not.exist').then(() => {
if(type === 'combinable') {
CreateProductPage.elements.radioBtnCombinableProduct().click()
}
CreateProductPage.elements.inputProductName().clear().type(name)
CreateProductPage.elements.inputPriceFieldGross().type(grossPrice)
SwPageHeader.elements.btnProductSave().click()
})
})
Questions:
This failed because of asynchronous nature of cypress?
If so, how to interrupt? Chaining with then(), the behavior is the same
With this is code (adding wait()) it works, but i'm looking for the right way
// my_test.cy.js
describe('product detail page', () => {
beforeEach(() => {
cy.login('admin', 'shop')
})
it('should print typed product', () => {
cy.createProduct(null, 'Component Product 0', '0')
CreateProductPage.elements.productDetailTabs().should('exist')
cy.wait(300)
cy.createProduct('combinable', 'Combined Test Product', '0')
CreateProductPage.elements.productDetailTabs().should('exist')
})
})
EDIT 1
// pages/CreateProductPage.js
class CreateProductPage {
elements = {
productDetailTabs: () => cy.get('div.sw-product-detail-page__tabs'),
radioBtnCombinableProduct: () => cy.get('.sw-product-detail-base__info input#type_combinable_product-0'),
radioBtnUnCombinableProduct: () => cy.get('.sw-product-detail-base__info input#type_combinable_product-1'),
inputProductName: () => cy.get('input#sw-field--product-name'),
inputPriceFieldGross: () => cy.get('div.sw-list-price-field__price input#sw-price-field-gross'),
}
}
module.exports = new CreateProductPage();
If the problem is one of waiting, you will need to figure out something that indicates to the user that the save was successful and test that.
For example, if there was a toast message on screen:
...
SwPageHeader.elements.btnProductSave().click()
cy.contains('span', 'Product was saved').should('be.visible')
Blockquote
This failed because of asynchronous nature of cypress?
Code behaves synchronously if you have only cy commands inside the code block.
As suggested in https://stackoverflow.com/a/74721728/9088832 you should wait for some element to appear or for an API request to be completed that is responsible for the product creation.

Doesn't work exception catching in Masstransit saga's

I try to use saga with MassTransit 7.2.2.
I have next event in my saga:
During(Submitted,
When(OrderAccepted)
.Then(x =>
{
logger.LogInformation($"Order {x.Instance.OrderId} accepted");
throw new Exception("TEST");
})
.Catch<Exception>(x =>
{
x.If(
context => context.Data.OrderId == 1002,
activityBinder =>
activityBinder
.Then(y =>
{
logger.LogInformation($"Order {y.Instance.OrderId} catch exception and pass to Rejected");
})
.TransitionTo(Rejected)
);
return x;
})
.ThenAsync(c =>
{
return TakeProductCommand(c);
})
.TransitionTo(Accepted));
In code above I want to catch any exception that might be raised during handling of current event and put saga into Rejected state. But it doesn't work as I thought. I don't get into the Catch handler at all.
What I did wrong?
Your code is slightly malformed, and breaks the builder pattern chain.
The changes are subtle, but significant.
During(Submitted,
When(OrderAccepted)
.Then(x =>
{
logger.LogInformation($"Order {x.Instance.OrderId} accepted");
throw new Exception("TEST");
})
.Catch<Exception>(x =>
x.If(
context => context.Data.OrderId == 1002,
activityBinder =>
activityBinder
.Then(y =>
{
logger.LogInformation($"Order {y.Instance.OrderId} catch exception and pass to Rejected");
})
.TransitionTo(Rejected)
)
)
.ThenAsync(c =>
{
return TakeProductCommand(c);
})
.TransitionTo(Accepted));

Why use immer for this function?

ToggleAttached function inside CauseSpecificPage.js. This function toggles the follow/following button on the cause specific page: st-bnv.
What is the benefit of using Immer in this situation? Do we even need to use Immer?
const ToggleAttached = () => {
if (state.isDisabled) {
return
}
const oldValue = state.isAttached
setState(produce((draftState) => {
draftState.isDisabled = true
draftState.isAttached = !oldValue
}))
ToggleFollow({ causeId })
.then(response => {
setState(produce((draftState) => {
draftState.isAttached = response.data.isAttached
}))
})
.catch(error => {
setState(produce((draftState) => {
draftState.isAttached = oldValue
}))
HandleError(error)
})
.finally(() => setState(produce((draftState) => {
draftState.isDisabled = false
})))}
Immer is a little more verbose but is more maintainable in the long run. Immer was created to help us to have an immutable state, it’s a library created based on the “copy-on-write” mechanism — a technique used to implement a copy operation in on modifiable resources.
We can see that the concept of immutability is getting used more and becoming more common in the React community. But to make sure that we’re doing it the right way, we can use Immer for the job. Immer adds more benefits on redux states.

Complete Function Before Remote Operation in NgRx

I'm having an issue with a race condition in NgRx. In the example below, I'm asynchronously presenting a loading dialog at about the same time as I'm starting an async remote operation. But the remote operation has the potential to complete and fire dismissLoadingDialog() before the loading dialog is fully built, which results in a console error.
What might be a good strategy in NgRx to complete presentLoadingDialog() before the remote operation begins?
#Effect() fetchServerData$ = this.actions$.pipe(
ofType<FetchServerData>(ActionTypes.FetchServerData),
switchMap(action => {
this.presentLoadingDialog('...loading');
return this.dataService.fetchData(action.payload).pipe(
map(result => {
this.dismissLoadingDialog();
return new FetchServerDataSuccess(result);
}),
catchError(err => of(new FetchServerDataFail(err)))
);
})
);
async presentLoadingDialog(message: string): Promise<void> {
this.isLoading = true;
return this.loadingCtrl
.create({
duration: 5000,
message: message
})
.then(loadingDialog => {
loadingDialog.present().then(() => {
if (!this.isLoading) {
loadingDialog.dismiss();
}
});
});
}
async dismissLoadingDialog() {
this.isLoading = false;
if (!isNullOrUndefined(this.loadingCtrl)): Promise<boolean> {
return this.loadingCtrl.dismiss();
}
}
Ionic's LoadingController create method returns a Promise which resolves when loader creation is complete. You can therefore use it in your effect's Observable chain:
presentLoadingDialog(message: string) {
const loader = this.loadingCtrl
.create({
duration: 5000,
message: message
});
return loader.present();
}
dismissLoadingDialog() {
this.loadingCtrl.dismiss();
}
#Effect() fetchServerData$ = this.actions$.pipe(
ofType<FetchServerData>(ActionTypes.FetchServerData),
switchMap(action => forkJoin(from(this.presentLoadingDialog('...loading'), of(action)),
switchMap(([_, action]) => this.dataService.fetchData(action.payload).pipe(
tap(() => this.dismissLoadingDialog()),
map(result => new FetchServerDataSuccess(result)),
catchError(err => {
this.dismissLoadingDialog();
return of(new FetchServerDataFail(err))
})
))
);
The standard I have seen is you have loading and loaded flags in your state. When you dispatch a load action the reducer updates the state with loading: true and loaded: false before the action fires the http request. The action then switch maps to an action that updates the state with the response and loading: false and loaded: true.
In your component you then have a selector for the loading flag and subscribe to it to open and close the dialog
this.loadingSub = loadings$.subscribe(loading => {
if (loading) {
this.presentLoadingDialog('...loading');
} else {
this.loadingDialog.dismiss();
}
});
unsubscribe in onDestroy
It should be up to your components to show UI components, I think actions calling loading dialogs is not an action concern. Tapping into the heart of state management to call UI components is not a pattern I would recommend.

Is It good to subscribe to an observable within another subscription handler?

I'm using Angular2 and I have a question about what is the best way to do if I have many observables.
Can I put subscriptions inside each other or put each one in a different method and put the results in class properties?
Example :
ngOnInit() {
this.route.params**.subscribe**(params => {
if (params['id']) {
this.load = true;
this.batchService.getPagesOfCurrentObject(params['id'], "10", "0")
**.subscribe**(result => {
this.stream = result;
if (this.stream.length > 0) {
this.stream.forEach(page => { this.batchService.getPageStreamById
(page.pageId)**.subscribe**(pageStream => {
let base64 = btoa(new Uint8Array(pageStream.data)
.reduce((data, byte)
=> data + String.fromCharCode(byte), ''));
this.pages.push(base64 );
})
return;
});
}
},
error => this.errorService.setError(<any>error),
() => this.load = false
);
}
});
try {
this.customer = this.sharedService.processSelect.subscription.customer;
} catch (err) {
return;
}
}
Having multiple observables is totally fine, this is what reactive programming is about :)
But here your problem is having too much subscribe. Keep in mind that subscribe is a way to create side effect. To have an easy to read code, you should try to use the least possible subscribe.
Your use case is the perfect use case for the mergeMap operator, that allows you to flatten nested observables.
Here what your code would look like
const response$ = this.route.params
.mergeMap(params => {
return this.batchService.getPagesOfCurrentObject(params['id'])
})
.mergeMap(stream => {
return Rx.Observable.merge(stream.map(page => this.batchService.getPageStreamById(page.pageId))
})
.map(pageStream => /* do your stuff with pageStream, base64 ... */)
response$.subscribe(pageStreamData => pages.push(pageStreamData))
See how there is a single subscription that triggers the side-effect that will modify your app's state
Note that I voluntarily simplified the code (removed error handling and checks) for you to get the idea of how to do that.
I hope it will help you thinking in reactive programming :)

Resources