- Introduction
- Application Architecture
- Design of Microservice
- Security : JWT Token based Authentication
- Development Environment
- Technologies
- Opensource Tools Used
- Cloud Platform Services
- Database Design
- WebApi Endpoints
- Solution Structure
- Exception Handling
- Db Concurrency Handling
- Azure AppInsights: Logging and Monitoring
- Swagger: API Documentation
- Postman Collection
- How to run the application
- Console App - Gateway Client
This is a .Net Core sample application and an example of how to build and implement a microservices based back-end system for a simple automated banking feature like Balance, Deposit, Withdraw in ASP.NET Core Web API with C#.Net, Entity Framework and SQL Server.
The sample application is build based on the microservices architecture. There are serveral advantages in building a application using Microservices architecture like Services can be developed, deployed and scaled independently.The below diagram shows the high level design of Back-end architecture.
- Identity Microservice - Authenticates user based on username, password and issues a JWT Bearer token which contains Claims-based identity information in it.
- Transaction Microservice - Handles account transactions like Get balance, deposit, withdraw
- API Gateway - Acts as a center point of entry to the back-end application, Provides data aggregation and communication path to microservices.
This diagram shows the internal design of the Transaction Microservice. The business logic and data logic related to transaction service is written in a seperate transaction processing framework. The framework receives input via Web Api and process those requests based on some simple rules. The transaction data is stored up in SQL database.
JWT Token based authentication is implementated to secure the WebApi services. Identity Microservice acts as a Auth server and issues a valid token after validating the user credentitals. The API Gateway sends the token to the client. The client app uses the token for the subsequent request.
- C#.NET
- ASP.NET WEB API Core
- SQL Server
- Automapper (For object-to-object mapping)
- Entity Framework Core (For Data Access)
- Swashbucke (For API Documentation)
- XUnit (For Unit test case)
- Ocelot (For API Gateway Aggregation)
- Azure App Insights (For Logging and Monitoring)
- Azure SQL Database (For Data store)
The application has four API endpoints configured in the API Gateway to demonstrate the features with token based security options enabled. These routes are exposed to the client app to cosume the back-end services.
- Route: "/user/authenticate" [HttpPost] - To authenticate user and issue a token
- Route: "/account/balance" [HttpGet] - To retrieve account balance.
- Route: "/account/deposit" [HttpPost] - To deposit amount in an account.
- Route: "/account/withdraw" [HttpPost] - To withdraw amount from an account.
- Route: "/api/user/authenticate" [HttpPost]- To authenticate user and issue a token
- Route: "/api/account/balance" [HttpGet]- To retrieve account balance.
- Route: "/api/account/deposit" [HttpPost]- To deposit amount in an account.
- Route: "/api/account/withdraw" [HttpPost]- To withdraw amount from an account.
- Identity.WebApi
- Handles the authentication part using username, password as input parameter and issues a JWT Bearer token with Claims-Identity info in it.
- Transaction.WebApi
- Supports three http methods 'Balance', 'Deposit' and 'Withdraw'. Receives http request for these methods.
- Handles exception through a middleware
- Reads Identity information from the Authorization Header which contains the Bearer token
- Calls the appropriate function in the 'Transaction' framework
- Returns the transaction response result back to the client
- Transaction.Framework
- Defines the interface for the repository (data) layer and service (business) layer
- Defines the domain model (Business Objects) and Entity Model (Data Model)
- Defines the business exceptions and domain model validation
- Defines the required data types for the framework 'Struct', 'Enum', 'Consants'
- Implements the business logic to perform the required account transactions
- Implements the data logic to read and update the data from and to the SQL database
- Performs the task of mapping the domain model to entity model and vice versa
- Handles the db update concurrency conflict
- Registers the Interfaces and its Implementation in to Service Collection through dependency injection
- Gateway.WebApi
- Validates the incoming Http request by checking for authorized JWT token in it.
- Reroute the Http request to a downstream service.
- SimpleBanking.ConsoleApp
- A console client app that connects to Api Gateway, can be used to login with username, password and perform transactions like 'Balance', 'Deposit' and 'Withdraw' against a account.
A Middleware is written to handle the exceptions and it is registered in the startup to run as part of http request. Every http request, passes through this exception handling middleware and then executes the Web API controller action method.
- If the action method is successfull then the success response is send back to the client.
- If any exception is thrown by the action method, then the exception is caught and handled by the Middleware and appropriate response is sent back to the client.
public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{
try
{
await next(context);
}
catch (Exception ex)
{
var message = CreateMessage(context, ex);
_logger.LogError(message, ex);
await HandleExceptionAsync(context, ex);
}
}
Db concurrency is related to a conflict when multiple transactions trying to update the same data in the database at the same time. In the below diagram, if you see that Transaction 1 and Transaction 2 are against the same account, one trying to deposit amount into account and the other system tring to Withdraw amount from the account at the same time. The framework contains two logical layers, one handles the Business Logic and the other handles the Data logic.
When a data is read from the DB and when business logic is applied to the data, at this context, there will be three different states for the values relating to the same record.
- Database values are the values currently stored in the database.
- Original values are the values that were originally retrieved from the database
- Current values are the new values that application attempting to write to the database.
The state of the values in each of the transaction produces a conflict when the system attempts to save the changes and identifies using the concurrency token that the values being updated to the database are not the Original values that was read from the database and it throws DbUpdateConcurrencyException.
The general approach to handle the concurrency conflict is:
- Catch DbUpdateConcurrencyException during SaveChanges
- Use DbUpdateConcurrencyException.Entries to prepare a new set of changes for the affected entities.
- Refresh the original values of the concurrency token to reflect the current values in the database.
- Retry the process until no conflicts occur.
while (!isSaved)
{
try
{
await _dbContext.SaveChangesAsync();
isSaved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is AccountSummaryEntity)
{
var databaseValues = entry.GetDatabaseValues();
if (databaseValues != null)
{
entry.OriginalValues.SetValues(databaseValues);
CalculateNewBalance();
void CalculateNewBalance()
{
var balance = (decimal)entry.OriginalValues["Balance"];
var amount = accountTransactionEntity.Amount;
if (accountTransactionEntity.TransactionType == TransactionType.Deposit.ToString())
{
accountSummaryEntity.Balance =
balance += amount;
}
else if (accountTransactionEntity.TransactionType == TransactionType.Withdrawal.ToString())
{
if(amount > balance)
throw new InsufficientBalanceException();
accountSummaryEntity.Balance =
balance -= amount;
}
}
}
else
{
throw new NotSupportedException();
}
}
}
}
}
Azure AppInsights integrated into the "Transaction Microservice" for collecting the application Telemetry.
public void ConfigureServices(IServiceCollection services)
{
services.AddApplicationInsightsTelemetry(Configuration);
}
AppInsights SDK for Asp.Net Core provides an extension method AddApplicationInsights on ILoggerFactory to configure logging. All transactions related to Deposit and Withdraw are logged through ILogger into AppInsights logs.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory log)
{
log.AddApplicationInsights(app.ApplicationServices, LogLevel.Information);
}
To use AppInsights, you need to have a Azure account and create a AppInsights instance in the Azure Portal for your application, that will give you an instrumentation key which should be configured in the appsettings.json
"ApplicationInsights": {
"InstrumentationKey": "<Your Instrumentation Key>"
},
Swashbuckle Nuget package added to the "Transaction Microservice" and Swagger Middleware configured in the startup.cs for API documentation. when running the WebApi service, the swagger UI can be accessed through the swagger endpoint "/swagger".
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c => {
c.SwaggerDoc("v1", new Info { Title = "Simple Transaction Processing", Version = "v1" });
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory log)
{
app.UseSwagger();
app.UseSwaggerUI(c => {
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Simple Transaction Processing v1");
});
}
Download the postman collection from here to run the api endpoints through gateway
- Download the Sql script from here,
- Run the scirpt against SQL server to create the necessary tables and sample data
- Open the solution (.sln) in Visual Studio 2017 or later version
- Configure the SQL connection string in Transaction.WebApi -> Appsettings.json file
- Configure the AppInsights Instrumentation Key in Transaction.WebApi -> Appsettings.json file. If you dont have a key or don't require logs then comment the AppInsight related code in Startup.cs file
- Check the Identity.WebApi -> UserService.cs file for Identity info. User details are hard coded for few accounts in Identity service which can be used to run the app. Same details shown in the below table.
- Run the following projects in the solution
- Identity.WebApi
- Transaction.WebApi
- Gateway.WebApi
- SimpleBanking.ConsoleApp
- Gateway host and port should be configured correctly in the ConsoleApp
- Idenity and Transaction service host and port should be configured correctly in the gateway -> configuration.json
- Sample data to test
Account Number | Currency | Username | Password |
---|---|---|---|
3628101 | EUR | speter | test@123 |
3637897 | EUR | gwoodhouse | pass@123 |
3648755 | EUR | jsmith | admin@123 |