ok thank you for explanation. I think that abp is easing so many things but at the same time it becomes very difficult for simple implementations. Maybe i should just create my own implementation for domain events instead of using local event bus. Anyway i will think about it. IDistributedEventBus is not the solution for me since i also use rabbitmq for my distributed messaging. I am closing this discussion over here. Thanks anyway.
Ok. So in my project i use LocalEventBus (for internal modules) and DistributedEventBus (for other api projects running with my main app). If i want to implement Inbox / Outbox pattern to my internal modules (with local events) i believe that is not possible with abp out of the box. Am i right about it? Inbox / Outbox pattern is only designed for distributed event bus?
If i didn't have any other api in my project i know that i could use DistributedEventBus. But that option no longer exists as i see it. I am aware with other complexities that it would bring if i deal with one dbcontext. But performance can be sth i can choose in that case. I believe i should implement that myself.
public virtual async Task CompleteAsync(CancellationToken cancellationToken = default)
{
if (_isRolledback)
{
return;
}
PreventMultipleComplete();
try
{
_isCompleting = true;
await SaveChangesAsync(cancellationToken);
DistributedEvents.AddRange(GetEventsRecords(DistributedEventWithPredicates));
LocalEvents.AddRange(GetEventsRecords(LocalEventWithPredicates));
while (LocalEvents.Any() || DistributedEvents.Any())
{
if (LocalEvents.Any())
{
var localEventsToBePublished = LocalEvents.OrderBy(e => e.EventOrder).ToArray();
LocalEventWithPredicates.Clear();
LocalEvents.Clear();
await UnitOfWorkEventPublisher.PublishLocalEventsAsync(
localEventsToBePublished
);
}
if (DistributedEvents.Any())
{
var distributedEventsToBePublished = DistributedEvents.OrderBy(e => e.EventOrder).ToArray();
DistributedEventWithPredicates.Clear();
DistributedEvents.Clear();
await UnitOfWorkEventPublisher.PublishDistributedEventsAsync(
distributedEventsToBePublished
);
}
await SaveChangesAsync(cancellationToken);
LocalEvents.AddRange(GetEventsRecords(LocalEventWithPredicates));
DistributedEvents.AddRange(GetEventsRecords(DistributedEventWithPredicates));
}
await CommitTransactionsAsync(cancellationToken);
IsCompleted = true;
await OnCompletedAsync();
}
catch (Exception ex)
{
_exception = ex;
throw;
}
}
so i should implement my own logic for local events here instead so i shouldn't publish the events maybe bg job can publish it after the transaction complete accordingly? That is going to be difficult for each module since each module needs to have the same code duplication for inbox / outbox pattern.
ok i got it Enis. Thank you for the explanation. So when you call unitOfWork.complete() it closes all the dbcontexts inside the unitofwork so before it comes to there if there is an exception all the dbcontexts are gonna be rolled back.
Just one more question. Does it have any performance problem since it is dealing with multiple db contexts with separate connection strings in one request? Cause i have seen in so many examples, they do not deal with unitofwork in multiple databases instead they have implemented inbox/outbox pattern and closing the transactions for each modules. So you can think they use separate unit of work for each module and use inbox/outbox pattern.
Hello Enis, Thanks for the reply. I understand the concept. I just want to give one example since i don't know how abp would react to it. Let's say I have one separate database for each module. Let's assume CreateOrder() appservice have been called from Ordering Module and as a side effect of the module i raised a local event OrderCreated. On Ticketing Module i want to issue a ticket for that event. OrderCreatedEventHandler catched the event and trying to insert a new ticket to Ticketing Database(which is separate from Ordering Database). If you can not reach the Ticketing Database at that time what is happening? Is UnitOfWork rolling back so the Ordering insert is also rolled back or eventual consistency is broken. I believe it is gonna be broken as you mentioned you can not create transaction between separate databases. Just correct me if i am wrong.
And if it is broken. What can i do to solve the problem? My solution could be using inbox / outbox pattern in that case. Even if it is a local event if you can implement inbox / outbox pattern, then you can raise the same event from inbox messages again after some time and correct the state for Ticketing module. Is Abp supports that case? I know Abp supports it for DistributedEventBus. Can i use it also for LocalEventBus? And is there any sample for it?
PS:
If you use LocalEventBus, it will be a local transaction, and whenever an exception occurs in the consumer side, the transaction from the publisher side will be rolled back.
so you say even if it is a different database, unitofwork is going to rollback the transaction? are you sure about it? Isn't unitofwork calling savechanges() for each dbcontext and closing the transaction at the end of unitofwork?
Hello I figured it out @yekalkan. The problem was, I was debugging it as https instead of http. That’s why it didn’t hit the breakpoint. But it could be nice for abp studio to get the values from the pod instead of yaml file
Hello again, i have tried your suggestion it works but it throws error whenever i try to write to output stream.
System.ObjectDisposedException: Cannot access a closed Stream.
at System.IO.MemoryStream.CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken)
at System.IO.AbpStreamExtensions.CreateMemoryStreamAsync(Stream stream, CancellationToken cancellationToken)
at System.IO.AbpStreamExtensions.GetAllBytesAsync(Stream stream, CancellationToken cancellationToken)
at Volo.Abp.Studio.Client.AspNetCore.AbpStudioMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
at Volo.Abp.Studio.Client.AspNetCore.AbpStudioMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
at Volo.Abp.Studio.Client.AspNetCore.AbpStudioMiddleware.InvokeAsync(HttpContext context, RequestDelegate next)
at Microsoft.AspNetCore.Builder.UseMiddlewareExtensions.InterfaceMiddlewareBinder.<>c__DisplayClass2_0.<<CreateMiddleware>b__0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)
so it throws many of these whenever i tried to serve a file with big size as you can imagine. I want to ask when the new version is going to be released without the error. And what is this middleware used for? is it for telemetry? if i do not use the middleware what am i missing?
Hello, Ok here is some snapshots. When i create the project with abp studio default it comes with those appsettings for dbmigrator.
as you can see maui app is pointing out to 44331. launchsettings for web app is 44329 instead.
i have seen that i have written tiered but it wasn't apparently as i see it now.
when i run the maui app for windows i got
just a note i am not so sure now if i selected tiered or not while creating the project from abpstudio. Just maybe i selected tiered but abpstudio created non-tiered project that could be the bug also. But when i look at the abpstudio.sln file here is the config.
"creatingStudioConfiguration": {
"template": "app",
"createdAbpStudioVersion": "0.9.18",
"tiered": "false",
"multiTenancy": "true",
"includeTests": "true",
"uiFramework": "mvc",
"databaseProvider": "ef",
"databaseManagementSystem": "postgresql",
"separateTenantSchema": "false",
"theme": "leptonx",
"themeStyle": "system",
"mobileFramework": "maui",
"publicWebsite": "false",
"optionalModules": "GDPR TextTemplateManagement LanguageManagement AuditLogging OpenIddictAdmin",
"socialLogin": ""
}
I am trying to make this work. So what i have done to fix the problem is. I wrote a power shell script that will get my env variables and after i intercept it i replaced it with the .env file. Here is the shell script that i have used.
param (
[string]$app,
[string]$context
)
# Change the context
kubectl config use-context $context
# Get the directory of the current script
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
# Get the pod name
$podName = kubectl get pods -n prod -l app=$app -o jsonpath='{.items[0].metadata.name}'
# Get the environment variables
$envVars = kubectl exec -n prod $podName -- printenv
# Define the output file path
$outputFilePath = Join-Path -Path $scriptDir -ChildPath "abpstudio.env"
# Write the environment variables to the output file
$envVars | Out-File -FilePath $outputFilePath
then i added this line to my app start which wasn't there. That's why i couldn't get the env variables.
AbpStudioEnvironmentVariableLoader.Load();
then the app is running like it should be with the env variables injected. My app port is 44389 in my local env.
It runs on that port.
So when i open up a browser and write https://localhost:44389 i can see my app running.
also when i write https://host.docker.internal:44389 i can see my app running
but when i try to reach my service from outside with domain name or internally like http://api i can not reach it and nginx throws an error like
I think this is the last part i need to figure it out. So can you explain it to me how the nginx is forwarded to my local computer. I know that there is a client docker container running in my machine and one server in my cluster. But it seems interception is not happening whenever i try to hit the nginx ingress.
ok I think I kind of understand what the problem is.
My deployment is sth like this.
as you can see if the env variables comes from configMapRef or secretMapRef then the problem occurs. I believe the code that reads the env variables are only looking at the deployment file yaml and try to take it from there. It does not actually looking at the pod itself. Am i right? I can not change the deployment file since i prepared my whole cluster accordingly. Is there anything that i can do to fix it.
Hello again,
I have already had the environment variables in my pod the thing is when i intercept, it does not reflect to .env file that is generated by abpstudio.
here is a snapshot that i take from dashboard from kubernetes.
so i believe sth is wrong with transferring the env variables.
and can you also explain how these env variables has been injected when you debug from your local? Is Volo.Abp.Studio.Client.AspNetCore package is responsible for that?