external service result mutates state of aggregate - domain-driven-design

My problem is that I don't know how to handle external calls that mutates the state but also needs validation before executing them
Here is my command handler
public async Task<IAggregateRoot> ExecuteAsync(Command command)
{
var sandbox = await _aggregateStore.GetByIdAsync<Sandbox>(command.SandboxId);
var response = await _azureService.CreateRedisInstance(sandbox.Id);
if (response.IsSuccess)
{
sandbox.CreateRedisDetails(response);
return sandbox;
}
sandbox.FailSetup(response.Errors.Select(e => e.Message));
return sandbox;
}
The problem here is that the sandbox aggregate needs to be in correct state before calling external service and I cannot satisfy both. My only idea here is to create separate method CanCreateRedisInstance that checks if aggregate state is valid and only then calls external service. What I don't like is that I introduce validation methods
public async Task<IAggregateRoot> ExecuteAsync(Command command)
{
var sandbox = await _aggregateStore.GetByIdAsync<Sandbox>(command.SandboxId);
if(!sandbox.CanCreateRedisInstance())
{
throw new ValidationExcetpion("something");
}
var response = await _azureService.CreateRedisInstance(sandbox.Id);
if (response.IsSuccess)
{
sandbox.CreateRedisDetails(response);
return sandbox;
}
sandbox.FailSetup(response.Errors.Select(e => e.Message));
return sandbox;
}
The other approach I thought of is to make whole process more cqrs-ish.
public async Task<IAggregateRoot> ExecuteAsync(Command command)
{
var sandbox = await _aggregateStore.GetByIdAsync<Sandbox>(command.SandboxId);
sandbox.ScheduleRedisInstanceCreation();
}
public void ScheduleRedisInstanceCreation()
{
if(RedisInstanceDetails != null)
{
throw new ValidationException("something")
}
RedisInstanceDetails = RedisInstanceDetails.Scheduled(some arguments);
AddEvent(new RedisInstanceCreationScheduled(some arguments));
}
The RedisInstanceCreationScheduled event is sent to queue and picked by event handler
which will call external service and based on result will create other events
public async Task ExecuteAsync(RedisInstanceCreationScheduled event)
{
var sandbox = await _aggregateStore.GetByIdAsync<Sandbox>(command.SandboxId);
var response = await _azureService.CreateRedisInstance(sandbox.Id);
if (response.IsSuccess)
{
sandbox.CreateRedisDetails(response);
return sandbox;
}
sandbox.FailSetup(response.Errors.Select(e => e.Message));
_aggregateStore.Save(sandbox);
}
However this approach add some extra complexity and I am not quite sure if event handler should modify aggregate.

Both approaches are possible.
Why no validation should stay in the Handler? When you change something in the domain, the domain object makes also a validation about the action, and deny it if it's not possible. Here you just need to interact with an external service to verify it.
The external service is just an interface in the domain layer, that you're going to implement with a concrete class into the infrastructure layer. Hence you will not have a directly binding with azure, but a service, let's say CloudService, that in it's implementation uses Azure. This allows you to build domain related exceptions that are thrown by classes that stay in the infrastructure layer.
Also the CQRS approach is valid. But you have to take care when you use it.
You can, for example, start a saga where you ask to the external service to create the instance (CreateRedisInstance), then, according to the event that you get (success or failure) you proceed with the next handler. But you really have to take care about middle situations: what should be done to handle failures between the 2 actions? You need also a rollback of the first action if the second one ends with a failure.
Said this, I would go with the first one if there're no really need to handle a complex process. Moreover, it looks that is all related to the same domain (no infra-domain actions are required), hence there're no real need to augment the complexity with a saga where every success/fail status should be correctly handled.

Related

Mikro-orm inter-service transactions in NestJS

I am evaluating Mikro-Orm for a future project. There are several questions I either could not find an answer in the docs or did not fully understand them.
Let me describe a minimal complex example (NestJS): I have an order processing system with two entities: Orders and Invoices as well as a counter table for sequential invoice numbers (legal requirement). It's important to mention, that the OrderService create method is not always called by a controller, but also via crobjob/queue system. My questions is about the use case of creating a new order:
class OrderService {
async createNewOrder(orderDto) {
const order = new Order();
order.customer = orderDto.customer;
order.items = orderDto.items;
const invoice = await this.InvoiceService.createInvoice(orderDto.items);
order.invoice = invoice;
await order.persistAndFlush();
return order
}
}
class InvoiceService {
async create(items): Invoice {
const invoice = new Invoice();
invoice.number = await this.InvoiceNumberService.getNextInSequence();
// the next two lines are external apis, if they throw, the whole transaction should roll back
const pdf = await this.PdfCreator.createPdf(invoice);
const upload = await s3Api.uplpad(pdf);
return invoice;
}
}
class InvoiceNumberService {
async getNextInSequence(): number {
return await db.collection("counter").findOneAndUpdate({ type: "INVOICE" }, { $inc: { value: 1 } });
}
}
The whole use case of creating a new order with all subsequent service calls should happen in one Mikro-Orm transaction. So if anything throws in OrderService.createNewOrder() or one one of the subsequently called methods, the whole transaction should be rolled back.
Mikro-Orm does not allow the atomic update-increment shown in InvoiceNumberService. I can fall back to the native mongo driver. But how do I ensure the call to collection.findOneAndUpdate() shares the same transaction as the entities managed by Mikro-Orm?
Mikro-Orm needs a unique request context. In the examples for NestJS, this unique context is created at the controller level. In the example above the service methods are not necessarily called by a controller. So I would need a new context for each call to OrderService.createNewOrder() that has a lifetime scoped to the function call, correct? How can I acheive this?
How can I share the same request context between services? In the example above InvoiceService and InvoiceNumberService would need the same context as OrderService for Mikro-Orm to work properly.
I will start with the bad news, mongodb transactions are not yet supported in MikroORM (athough they will land within weeks probably, already got the PoC implemented). You can subscribe here for updates: https://github.com/mikro-orm/mikro-orm/issues/34
But let me answer the rest as it will then apply:
You can use const collection = (em as EntityManager<MongoDriver>).getConnection().getCollection('counter'); to get the collection from the internal mongo connection instance. You can also use orm.em.getTransactionContext() to get the current trasaction context (currently implemented only in sql drivers, but in future this will probably return the session object in mongo).
Also note that in mongo driver, implicit transactions won't be enabled by default (it will be configurable though), so you will need to use explicit transaction demarcation via em.transactional(...).
The RequestContext helper works automatically. You just register it as a middleware (done automatically in the nestjs orm adapter) and then your request handler (route/endpoint/controller method) is ran inside a domain that shares the context. Thanks to this, all services in the DI can share singleton instances of repositories, but they will automatically pick the right context from the domain.
You basically have this automatic request context, and then you can create new (nested) contexts manually via em.transactional(...).
https://mikro-orm.io/docs/transactions/#approach-2-explicitly

Complexity level / responsibility for Azure Functions

We are switching over our Azure WebJobs to Azure Functions (for multiple reasons besides this post). But we internally can't really agree on the architecture for those functions.
Currently we have one WebJob that does a single tasks from A-Z.
E.g. Status emails: (Gets triggered from the scheduler)
1. Looks up all recepients
2. Send email to all of those
3. Logs success/failure for each individual recepient
4. Logs success/failure for the whole run
And we have multiple web-jobs that do similar tasks.
Now we have 3 ways we can implement this in the future.
One-to-one conversion. Move the complete WebJob functionality into one Azure function
Split the above process into 4 different Azure functions. E.g. one that looks up the recipients and then calls another function that sends out the email and so on.
Combine all WebJobs into one Azure function
Personally, I would tend towards solution 3. But some team members tend towards 1. What do you think?
I would go with option 3 too. You can use Durable Functions and let it control the workflow, and create activities for each step (1-4).
[FunctionName("Chaining")]
public static async Task<object> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
try
{
var recipients = await context.CallActivityAsync<object>("GetAllRecipients", null);
foreach(var recipient in recipients)
{
//maybe return a complex object with more info about the failure
var success = await context.CallActivityAsync<object>("SendEmail", recipient);
if (! success)
{
await context.CallActivityAsync<object>("LogError", recipient);
}
}
return await context.CallActivityAsync<object>("NotifyCompletion", null);
}
catch (Exception ex)
{
// Error handling or compensation goes here.
await context.CallActivityAsync<object>("LogError", ex);
}
}
more info: https://learn.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-overview

Azure Durable Function (external functions)

I developed a couple of microservice using Azure functions, every service has independent use case and different programming language.
Now I have a use case to use all service in below order, So I developed one more Azure function to use all service in given order. below code running well.
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
string returnValue = string.Empty;
dynamic data = await req.Content.ReadAsStringAsync();
if (data == null)
{
return req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a value in the request body");
}
else
{
string body = data.ToString();
var transformResult = await HttpRestHelper.CreateRequestAsync(AppConfiguration.TransformServiceEndPoint, body, HttpMethod.POST);
var validationResult = await HttpRestHelper.CreateRequestAsync(AppConfiguration.ValidationServiceEndPoint, transformResult.Result.ToString(), HttpMethod.POST);
if (validationResult.Result != null && Convert.ToBoolean(validationResult.Result))
{
var encryptionResult = await HttpRestHelper.CreateRequestAsync(AppConfiguration.EncryptionServiceEndPoint, transformResult.Result.ToString(), HttpMethod.POST);
var storageResult = await HttpRestHelper.CreateRequestAsync(AppConfiguration.StorageServiceEndPoint, encryptionResult.Result.ToString(), HttpMethod.POST);
returnValue = storageResult.Result.ToString();
}
else
{
returnValue = "Validation Failed";
}
return req.CreateResponse(HttpStatusCode.OK, returnValue, "text/plain");
}
}
Question
If every microservice takes 1 min to execution, I have to wait ~4min in my Super Service and billed for 4+ min. (We don't need to pay for waiting time :) https://www.youtube.com/watch?v=lVwWlZ-4Nfs)
I want to use Azure Durable functions here but didn't get any method to call external url.
Please help me or suggest a better solution.
Thanks In Advance
Durable Orchestration Functions don't work with arbitrary HTTP endpoints. Instead, you need to create individual functions as Activity-triggered.
Orchestration will use message queues behind the scenes rather than HTTP. HTTP is request-response in nature, so you have to keep the connection and thus pay for it.
Queue-based orchestrator can also give you some extra resilience in face of intermittent failures.

How to change CacheClients at runtime in ServiceStack?

I'd like (through app/web configuration perhaps) to change the cache client used in my ServiceStack application, during runtime.
For example, I have this currently:
container.Register<ICacheClient>(new MemoryCacheClient());
I'd like at runtime to change this to a Redis ICacheClient usage. What if I had two containers registered (one Memory and on Redis). Is it possible to switch between containers at runtime in a call like this in my service:
public object Get(FooRequest request)
{
string cacheKey = UrnId.CreateWithParts("Foo", "Bar");
return RequestContext.ToOptimizedResultUsingCache(base.Cache, cacheKey, sCacheDuration, () =>
{
return TestRepository.Foos;
});
}
EDIT:
Note, after more research, if you have more than one ICacheClient registered:
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager("localhost:6379"));
container.Register(c => c.Resolve<IRedisClientsManager>().GetCacheClient());
container.Register<ICacheClient>(new MemoryCacheClient());
Then accessing base.Cache within your service will return the most recent ICacheClient that was registered... ie: in the case above, MemoryCacheClient.
So with the ability to access the Cache object from within the service, I'd just need a way to get a particular Cache from my registered caches, which I can't see any property for.
Doing something like this would allow you to register different providers with the container based on a web config setting:
var redisCacheString = ConfigurationManager.AppSettings["UseRedis"];
var useRedis = false;
if (!bool.TryParse(redisCacheString, out useRedis))
{
container.Register<IRedisClientsManager>(c => new PooledRedisClientManager("localhost:6379"));
container.Register(c => c.Resolve<IRedisClientsManager>().GetCacheClient());
}
else
{
container.Register<ICacheClient>(new MemoryCacheClient());
}
Hope that helps!
It seems to me that you'll need more flexibility rather than just a simple registration on the composite root, you can try to implement the composite pattern in your container registration.
steven explains this pattern using simple injector but I think it can be implemented with the IOC provided OOB by SS or any other
I hope that helps

Azure Service Bus - SubscriptionClient.AcceptMessageSession() vs. SubscriptionClient.BeginAcceptMessageSession()

In the Azure Service Bus namespace, there is a SubscriptionClient type, with a method to initiate a MessageSession in this manner:-
MessageSession session = subscriptionClient.AcceptMessageSession(...);
This is the synchronous version, and it returns a MessageSession. The library also provides an asynchronous version, BeginAcceptMessageSession(). This one is tripping me up, because it invokes a callback, passing in an IAsyncResult and whatever state object you wish to pass. In my case, I am passing the SubscriptionClient instance, so that I can invoke EndAcceptMessageSession() on the SubscriptionClient. BeginAcceptMessageSession() has a return type of void.
How can I access the MessageSession that is accepted via BeginAcceptMessageSession()? All I get back in the callback's result parameter is my SubscriptionClient instance, which I need in order to terminate the BeginAcceptMessageSession() via EndAcceptMessageSession().
The MessageSession reference is nowhere to be found. The documentation is no help in this regard. Searching on Google only reveals a scant 3 pages of search results, most of which is simply the online description of the method itself from MSDN. I looked in AsyncManager.Parameters and it is also empty.
Does anyone know how BeginAcceptMessageSession() is supposed to be invoked so that I can get a reference to the MessageSession thus created?
You should invoke the method like this:
Call the begin method with a method that accepts the IAsyncResult and the SubscriptionClient.
In the other method (AcceptDone in this case), call EndAcceptMessageSession with the IAsyncResult to get the MessageSession
What you see here is an standard implementation of the Asynchronous Programming Model.
private static void Do()
{
SubscriptionClient client = ...
client.BeginAcceptMessageSession(AcceptDone, client);
}
public static void AcceptDone(IAsyncResult result)
{
var subscriptionClient = result.AsyncState as SubscriptionClient;
if (subscriptionClient == null)
{
Console.WriteLine("Async Subscriber got no data.");
return;
}
var session = subscriptionClient.EndAcceptMessageSession(result);
...
subscriptionClient.Close();
}

Resources