So for a hobby project of mine, I would like to create an application that translates an HTTP call and request between two services.
The application does that based on a configuration that can be set by the user. The idea is that the application listens to an incoming API call translates the call and then forwards it.
Then the application waits for a response then translates the response and sends it back to the caller.
A translation can be as simple as renaming a field value in a body object or replace a header field to the body.
I think a translation should begin with mapping the correct URL so here is an example of what I was thinking of a configuration should look like:
//request mapping
incoming URL = outgoing URL(
//Rename header value
header.someobject.renameto = "somevalue"
//Replace body object to header
body.someobject.replaceto.header
)
I was thinking that the configuration should be placed in a .txt file and read by the application.
My question is, are there other similar systems that use a configuration file for a configuration like this? And are there other/better ways to declare a configuration?
I have done something sort-of-similar in a different context (generate code from an input specification), so I will provide an outline of what I did to provide some food for thought. I used Config4* (disclosure: I developed that). If the approach I describe below is of interest to you, then I suggest you read Chapters 2 and 3 of the Config4* Getting Started Guide to get an overview of the Config4* syntax and API. Alternatively, express the concepts below in a different configuration syntax, such as XML.
Config4* is a configuration syntax, and the subset of syntax relevant to this discussion is as follows:
In a code generator application, I used a table to provide rules to specify how to generate code for assigning values to fields of messages. If no rule was specified for a particular field, then some built-in rules provided default behaviour. The table looked something like the following:
When my code generator wanted to generate code to assign a value to a field, the code generator constructed a string of the form
"<message-name>.<field-name>", for example,Msg3.price. Then it examined thefield_rulestable line-by-line (starting from the top) to find a line in which the first column matched"<message-name>.<field-name>". The matching logic permitted*as a wildcard character that could match zero or more characters. (Conveniently, Config4* provides apatternMatch()utility operation that provides this functionality.)If a match was found, then the value in the
instructioncolumn told the code generator what sort of code to generate. (If no match was found, then built-in rules were used, and if none of those applied, then no code was generated for the field.)Each instruction was a string of the form
"@<keyword>:optional,arguments". That was tokenized to provide the keyword and the optional arguments. The keyword was converted to anenum, and that drove aswitchstatement for generating code. For example:@config:usernameinstruction specified that code should be generated to assign the value of theusernamevariable in a runtime configuration file to the field.@order:priceinstruction specified that code should be generated to assign the value returned from callingorderObj->getPrice()to the field.@string:foobarinstruction specified the string literalfoobarshould be assigned to the field.@expr:_heartbeatInterval * 1000instruction specified that code should be generated to assign the value of the expression_heartbeatInterval * 1000to the field.@ignoreinstruction specified that no code should be generated to assign a value to the field.@nowinstruction specified that code should be generated to assign the current clock time to the field.I have used the above technique in several projects, and each time I have invented instructions specific to the needs of the particular project. If you decide to use this technique, then obviously you will need to invent instructions to specify runtime translations rather than instructions to generate code. Also, don't feel you have to shoehorn all of your translation-based configuration into a single table. For example, you might use one table to provide a source URL -> destination URL mapping, and a different table to provide instructions for translating fields within messages.
If this technique works as well for you as it has worked for me on my projects, then you will end up with your translation application being an "engine" whose behaviour is driven entirely by a configuration file that, in effect, is a DSL (domain-specific language). That DSL file is likely to be quite compact (less than 100 lines), and will be the part of the application that is visible to users. Because of this, it is worthwhile investing effort to make the DSL as intuitive and easy-to-read/modify as possible, because doing that will make the translation application: (1) user friendly, and (2) easy to document in a user manual.