Leverage Wolverine's A-Frame architecture support

Search for a command to run...

No comments yet. Be the first to comment.
A-Frame architecture helps separate infrastructure from logic through mediation of a controller. In doing so, it simplifies a lot of scenarios and transforms them into trivially testable code.
The examples in the previous posts seem really nice for simple scenarios. How do I approach more advanced use cases? The big scenarios are: I need multiple pieces of data from different sources I need to perform an infrastructure call in the middle...
Phew, that was a lot. I'm always surprised how simple solutions contain so much detail and nuance when I try to explain them. I hope I conveyed the message that A-Frame simplifies code within a module or class by separating the infrastructure compone...

No architecture is complete without an easy way to test the functionality. My recommended strategy is to use two types of tests: unit and integration. As far as test setup goes, there is no clear winner for a test framework. xUnit.NET, NUnit and the ...

The examples in the previous posts seem really nice for simple scenarios. How do I approach more advanced use cases? The big scenarios are: I need multiple pieces of data from different sources I need to perform an infrastructure call in the middle...

Now the expected structure is clear, let's take a look at the code. I will start with a minimal API implementation to demonstrate that there is no need for a framework to implement this architecture. I do know of a framework that makes A-Frame effort...

I’ve shown how I write code using the A-Frame architecture without help. Yet I find a library or framework very convenient when using advanced techniques. Wolverine is at its core a messaging framework, but goes well beyond that. It has an in-memory transport, so it behaves like a mediator, but with all the quality of life improvements that apply to any other transport. It supports durable messaging via the in- and outbox patterns, retries, timeouts, error handling and much more. The in-memory transport is great for integration testing as well. Once the app goes live, it can switch the transport out for an external system such as RabbitMQ, Azure Service Bus, Amazon SQS/SNS, Apache Kafka and even SQL server or PostgreSQL if I have limited messaging needs. It also supports messages coming in through HTTP requests, so I can build an API with it.
Wolverine installs alongside other messaging and API frameworks such as MassTransit, NServiceBus, minimal API, ASP.NET and Blazor. This allows gradual integration within existing projects. For this purpose, I will leave the functionality of creating and retrieving a dog in the minimal API.
Let me demonstrate how simple Wolverine can be with another simple example: a walk. To create a walk, I have to map the path I walked and which dogs I had with me. In a real system, I'd look for GPS integration. For this demo system I use a list of coordinates [(0,0), (0,1), (2, 1), ...]. I also want to verify that the dogs are in the system, so I don't have unknown dogs with me.
public class RegisterWalkHandler
{
public Task<Dog[]> LoadAsync(RegisterWalk request, DogWalkingContext db) =>
db.Dogs.Where(d => request.DogsOnWalk.Contains(d.Name)).ToArrayAsync();
public ProblemDetails Validate(RegisterWalk request, Dog[] knownDogs)
{
var knownNames = knownDogs.Select(d => d.Name);
var unknownDogs = request.DogsOnWalk.Except(knownNames).ToArray();
return unknownDogs.Any()
? new ProblemDetails
{
Status = (int)HttpStatusCode.BadRequest,
Title = "Unknown dog or dogs",
Detail = string.Join(", ", unknownDogs)
}
: WolverineContinue.NoProblems;
}
[WolverinePost("/walk", Name = "Register Walk", OperationId = "Walk")]
[Tags("MoreThanCode.AFrameExample")]
public (LazyCreationResponse<WalkResponse> response, EntityFrameworkInsert<WalkWithDogs> insertWalk) Handle(
RegisterWalk request,
Dog[] dogsOnWalk)
{
var walk = new WalkWithDogs
{
Dogs = dogsOnWalk,
Path = request.Path.Select((coord, index) => new CoordinateEntity
{
X = coord.X,
Y = coord.Y,
SequenceOrder = index
})
.ToList()
};
var response = () => new WalkResponse(walk.Id, dogsOnWalk.Select(d => new DogResponse(d)).ToArray(), request.Path);
return (LazyCreationResponse.For(() => $"/walk/{walk.Id}", response),
new EntityFrameworkInsert<WalkWithDogs>(walk));
}
}
Wolverine works by convention: first it loads data when it sees a Load or LoadAsync function, then it validates the request, and finally it processes the Handle or Consume function. The Load function is equivalent to the infrastructure code, and the Handle function is my logic code. The Validate function is a bonus to even further separate concerns.
Through source generation, it creates the controller for me. This means that the code is fast because there is no reflection during runtime. It also figures out what to inject into each function. It can find injectable services through the configured dependency injection framework, it can inject messages from the bus and HTTP pipeline (including query parameters and form data) and objects returned from the Load function. That is how the Validate and Handle methods get the request and the list of dogs.
What I find most impressive is that Wolverine can handle the output of the Handle function. In an HTTP handler, the first item in the returned tuple is the response to the client. The next items will be either messages to be sent to the configured bus or handled as a side effect. A side effect is anything related to infrastructure that is part of the transaction: saving contents to a file, updating rows in the database,... It makes this distinction by looking for the presence of the ISideEffect marker interface.
For other side effects, especially concerning external systems, I put a command on the bus and have a handler interact with the external system. This has the benefit that it runs asynchronously, it’s possible to distribute processing and it uses built-in resiliency and retry mechanisms. It also promotes reuse as multiple components can trigger the integration with a simple message.
These are the most important features to get started with A-Frame architecture. For more in-depth knowledge I'll refer to the Wolverine docs.
Next up, I’ll dive into a more complex example.