Heredia, Costa Rica, 2022-11-05
Contrary to what you may have heard in the past, DRY (Don't Repeat Yourself) is not only not dead, but it arguably started with configuration values. It all goes back in the days where people were using magic numbers and magic strings everywhere in the code, until someone said: "Stop the madness! Declare them centrally and give them a meaningful name."
The thing eventually evolved to configuration systems that are flexible and intelligent, and do you know who has one of the best configuration systems ever? Yes! .Net Configuration is an amazing configuration system (so good, I actually couldn't live without it when I had to program JavaScript, so I made one for JavaScript).
Preface
What Is Ocelot?
Ocelot is a .Net library distributed as a Nuget package. This library has a bunch of ASP.Net middleware that allows the developer to quickly build a gateway (pretty much an HTTP proxy). It is primarily helpful in the microservices arena where you have a bunch of mini HTTP servers that, as a whole, comprise a system. The gateway's job is to provide a central location where API consumers can connect to, so as to achieve independence between what's public to consumers, and what's private to the system (the internal architecture).
What Is .Net Configuration
.Net Configuration is a .Net-provided feature that can hold configuration values in a hierarchical tree defined by keywords separated by semicolons (:). The configuration system allows a multitude of data sources, and the end result is calculated by merging all data sources into a single hierarchical dictionary. Any data source that is added has the capability of overriding values defined in previous data sources. This overriding feature is what we want to help us keep the configuration DRY. With value overrides, we can simply make one big, all-environment configuration file, and then simply create smaller per-environment data sources that do small overrides as the environments require.
Ocelot's Current Configuration Mechanism
Ocelot is configured through the .Net Configuration system. It expects a Routes
property at the top level, and its value must be an array that defines all possible routes in your system. The problem is, that because .Net Configuration cannot override values at the array element level, you are pretty much forced to repeat your entire routes configuration in each of the per-environment configuration data sources, failing the DRY challenge. Failing this challenge puts pressure in your code maintenance. Now any route change must be repeated as many times as environments you have (as a general rule of thumb).
Furthermore, each individual route member in the Routes
array must individually define things like host name, port and scheme, making the array itself not DRY. You must repeat this information for all the different routes you want to define for your microservices.
Redefining the Ocelot Configuration
In light of the explained problem, I am on the path of making a small .Net library called wj.Ocelot.Configuration that redefines the hierarchical structure of Ocelot's Routes
configuration property.
It redefines the configuration as follows:
{
"RootPath": "<gateway's root path>",
"RouteGroupA": {
"Host": "<host>",
"Port": <port>,
"Scheme": <scheme>,
"RootPath": <group's root path>,
"Priority": <priority for all routes in the route group>,
"Routes": [
"DownstreamPathTemplate": "<DS template>",
// etc. Typical Ocelot route configuration.
]
}
Advantages
The new configuration model allows to have per-route-group properties that apply to all routes, while individual routes maintain the ability to set their own values if required. The two most notable properties right now are Priority
and TimeOut
. They can be set at the route group level to avoid repetition.
The other advantage is that, because route groups are named, we can target per-environment overrides easily with the .Net Configuration system.
NOTE: Right now I have kept the Routes
property inside the route groups as an array, as I believe the remaining properties inside probably will never change per-environment. However, because this package is in its early stages, I can exchange it for a dictionary approach so individual route values can be targetted for override by .Net Configuration. Open an issue at the project's homepage in GitHub and let me know your preference.
Early Design Phase
This project is in the early design phase, and although the current code works properly in my early tests, it still needs some greater support for properties that are collections, such as UpstreamHttpMethod
.
Early Feedback is Welcome!
If you are currently using Ocelot, or have used it in the past, please, by all means go to this open issue and let me know which Ocelot properties you do/used to configure the most often.