Service Overview
Our infrastructure is very heavily based on a micro-service architecture with a variety of services that are currently exposed as REST APIs.
Note that the same services can also be hosted in a variety of ways, thanks to the use of CODE Framework Service infrastructure, and they have in fact been hosted in other formats in the past, such as SOAP-based XML web services and TCP/IP based binary services.
Hosting and Deployment
Our services are hosted on the Azure Cloud as web applications, usually in Linux-based distributions. (There are a few minor exceptions, in cases where services require Windows features).
Service Calling
Since services are entirely based on REST standards, they can be called from any language capable of making HTTP-calls. In addition, since our services are written in C#, we expose rich contract packages that can be downloaded from our internal package repository (on Azure DevOps). This makes calling services easier for .NET developers, especially when used in conjunction with CODE Framework Service features, suchas ServiceClient.Call<>()
.
Here is an example that calls the BlogService to get a list of all blogs we currently are running, using C#:
ServiceClient.Call<IBlogService>(s =>
{
var response = s.GetBlogPosts(new GetBlogPostssRequest { BlogId = new Guid("027f729b-21a1-4195-b18a-4fd7eb583750") });
if (response != null && response.Success)
foreach (var post in response.Posts)
Console.WriteLine(post.Title);
});
This is very convenient in a strongly typed language like C#, as it provides full-fidelity strong-typing and code safety. Also, note that there is nothing in this implementation that indicates REST service use. This service could also be defined elsewhere and run with a different protocol. Using ServiceClient
, this becomes a configuration issue, and this is a major reason why we had such code logngevity without building up a lot of technical debt.
Note: For this to work, there must be configuraiton in the system that tells ServiceClient
where IBlogService
can be found. For instance, a full-framework ASP.NET web project could have a web.config
file that includes the following configuration:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="ServiceProtocol" value="REST" />
<add key="RestServiceUrl:IBlogService" value="https://epsservices-blogs.azurewebsites.net" />
<!-- More settings omitted for brevity -->
</appSettings>
<!-- More config omitted for brevity -->
</configuration>
However, since this is “just a REST service” behind the scenes, it is also easy to call the same service from anything that can make HTTP requests. For instance, the following URL provides the same information (this is really what is called behind the scenes):
https://epsservices-blogs.azurewebsites.net/BlogPosts/027f729b-21a1-4195-b18a-4fd7eb583750
Note that this call uses default parameter values for many of the options that can be set on the service call. For more information, check out the individual services.
Would be nice to add some JavaScript and Python examples here.
Service Structure
All our services expose their structure (“contract”). For .NET developers, it is best to add a contract package for each service from our Azure DevOps package repository. (https://dev.azure.com/eps-software/EPSPackages).
For everyone else, dynamic calls can be made, but the structure is still well defined. Each of our services exposes a “Swagger File” that exposes the service structure in a standardized way. This definition can be used by tools such as Postman or Swagger UI to find out more about the service. (Also, during development, all our services launch into Swagger UI directly to make it easy to test the service). As an example, the Swagger File for the BLogs service can be found here: https://epsservices-blogs.azurewebsites.net/openapi.json. (As you can see, this is the main service URL followed by /openapi.json).
Service Development and Maintenance
Services are developed in .NET. Both Visual Studio and VS Code are used for development (and there is no reason other IDEs couldn't be used, since everything is based on standards).
To modify a service, the service's repository has to be cloned from Azure DevOps.
Services have to be configured properly in order to run. This usually means getting appropriate user secret confiuration set up for each service, since user names and passwords are not in the Git repositories. (Ask your manager if you are unsure how to configure the services to run). Also, since services usually access SQL Server databases, your IP address has to be whitelisted on the database in question.
The Ping Service
All our services implement Ping()
, which means they can be pinged to see if they are operational and what their basic deployment information (such as version) is. A service can usually be pinged just by typing the service URL, followed by /ping
into the browser. Here is an example URL:
https://epsservices-blogs.azurewebsites.net/ping
This URL provides basic information about the service in question (our Blog service, in this case).
Here is an example response:
{
"serverDateTime": "2024-10-04T06:06:09.0784891+00:00",
"version": "5.6.0+2e0a7889666127c8ffb6da46508d10857c81f403",
"operatingSystemDescription": "Debian GNU/Linux 12 (bookworm)",
"frameworkDescription": ".NET 8.0.7",
"codeFrameworkVersion": "2.0.17",
"success": true,
"failureInformation": ""
}
Logging and Error Handling
Our services log using the CODE Framework LoggingMediator
with the DatabaseLogger
class found in the EPS.Thessaly.Extras NuGet package
. Currently, all services log to the EPSLogs database on the code-sql01.database.windows.net
server. The connection string is configured in the service's secrets.json
file. As an example:
{
"LogServer-ConnectionString": "Server=code-sql01.database.windows.net;Database=EPSLogs;User Id=[user id];Password=[password];MultipleActiveResultSets=true;"
}
This overrides the setting in appsettings.json (don't write sensitive connection strings in appsettings.json):
{
"LogServer": {
"ConnectionString": ""
}
}
Configure the logger (typically in Program.cs
) as follows passing the name of the service as the second parameter:
LoggingMediator.AddLogger(new DatabaseLogger(builder.Configuration[logServerConnectionString], "Communications Service"));
The ApplicationLog
table has the following columns:
When viewing logs from the database, it's often best to return the results of your query as text instead of in a grid to preserve line feeds and other formatting.