ASP.NET Core, One transaction per server roundtrip
Transaction management is hard, but it doesn’t always have to cause you headaches. In this article I will show you how to setup a transaction per http request (or in one server-round-trip) for your WEBAPI RESTful endpoint.
We typically structure our API’s into logical business endpoints :
This allows us to hide a large part of the business logic behind a facade (or interface) and handle it in managed code, within our domain.
Ideally, these pieces of logic are wrapped in one single unit of execution, so that if something goes wrong, you can undo all the modifications to the database and try again. This is known as a database transaction.
For instance, image a business logic API pipeline :
- Add temporary record to the database
- Check external API to see if we can proceed
- Remove temporary record from the database
- Do business logic that generates 3 new records in the database
- Get the newly stored items from the database
- Serialize to JSON for HTTP Response
Here’s a visual of the workflow :
Let’s say, any of these steps may fail. The database may be offline, or the external endpoint may return a 500 error, we might not have access to delete records from the database, the JSON serialization may fail, etc.
Bottom line is, you may not want any inconsistent data in you database, this is hard to debug and resolve, leads to confusion and pollution.
One (simple) solution is to wrap it all up into one database transaction, which can be rolled back in case of failure (or by choice).
Let us consided the UOW (Unit Of Work), which will span 1 piece of business logic (creating a user, checking out a cart, processing an order).
The Unit Of Work will encapsulate the business logic in a transaction and commit (save to the database) the transaction or roll back (discard changes) in case of failure (exception).
To put this into action for an ASP.net core project, we can translate this in an ActionFilter.
If we look at the Filter and Middleware pipeline for a net core project, we can see the similarities.
When a request comes in, it is handled by a pipeline, middleware is executed and eventually your WEBAPI controller is executed.
The routing middleware decides which Controller it needs to instantiate based on the route (/api/users/234 ⮕ UsersController.GetUserById(234).
There are several kinds of Filters, most of them are self explanatory.
Each type of filter serves its purpose, typically you’ll write ActionFilters, based on the IActionFilter interface. Or you could write your own ActionFilterAttribute, so that you can use it as an attribute on the controller class or a controller method.
For our Unit Of Work, we will write a UnitOfWorkFilter, it will be put into the pipeline directly, permanently.
So that it is executed on each REST call.
The IDbTransaction is injected, we’ll talk about injection in the section below, for now, just assume it is provided upon creation of the class.
When an action (REST call) is executed, we’ll get the connection from the transaction, check if the connection is the open state and call any next item in the pipeline. This can be another action filter, or a controller method or a resultfilter that modifies the response (JSON, XML, CSV).
Make note that the next call will execute its successor and that one calls his succeeding pipe in the pipeline until the chain is complete. Whenever that completes, we will capture it in the UnitOfWorkFilter at line 20 and check if an exception occurred. If so, we roll back the tranaction (line 23) else we commit the changes to the database.
Now comes the part of how to register this into the pipeline. This is done via the built-in dependency injection mechanism of ASP.net core.
The troublesome task about DI, is the one of figuring out what lifetime your registered service needs to support.
ASP.net core’s DI library provides the following lifetimes:
If you want more details about the DI in ASP.net core, please look at the official msdn page.
A singleton service (in ASP.net core) is created once (and only once) the application comes up. This makes it ideal for caching services or calculation engines. The latter, for example, is a service that generates the same output for the same provided input. This can also be thought of as a ‘pure function’ or a collection of pure functions.
You may ask, why not use a static class as replacement for a singleton service ?
Although it is true that static classes are instantiated once for the application domain (as like singleton services), the DI library provides a way to register an implementation for a specific type of service. This allows you to register a fake or mock implementation whenever you’re running your tests and require a specific output for your singleton service.
A scoped service is instantiated every time a request comes in, each request gets its own instance of the service. After the request is terminated, the service is disposed.
This is ideal for our UnitOfWork, since it needs to be unique per-request and needs to span the lifetime of the request.
The same goes for the transaction, this needs to be a per-request resources as well.
Other types of scoped services could be, the authenticated user within the context of your application or other services that are specific to the request.
A transient service is instantiated each time it is requested. Keep in mind that any state that is put into this service, is only available to the consumer of this service and not to any other consuming services.
Database access classes (like repositories) can be used this way, for as long as they do not contain shared state.
Dependency Injection is more than just a registry, it manages the lifetime of the registered services, creates instances and injects the instantiated services into their consumers.
Other libraries, like AutoFac, are more extensive, I’ll try to keep it simple and plain, but if you need more control, you might want to check out other DI libraries.
It is considered a good practice to write helper functions for registering homogeneous services, it also makes for cleaner code.
The bootstrapping of your application is done via the Startup class in ASP.net core, here we’ll register our services. It’s also considered good practice to wrap all homogenous bootstrap code into helper functions. This also makes for cleaner code.
Consider lines 31 through 34, we get a connection string from configuration for later use. First we register all services that implement a certain type (IService), this would be our BusinessLogic, Repositories, Services, etc… To have marker interface (like IService) allows us to collect all services from an application assembly and register them in one line of code.
Lines 33 and 34 represent the homogeneous helper functions that setup SqlServer and the Unit Of Work (+ transaction) per-request (http call).
Here’s the implementation from those helper functions.
That’s it for the bootstrapping, you should now be able to register services (IService) and inject a IDbTransaction straight into the service via the constructor. I’ve created a repo with an example implementation and tests for demonstration.
All code can be found here, I hope you find it useful.