Three unobvious examples of using template engines in the backend
On the one hand, the subject was indeed square. On the other hand, it was round. But on the third side, with which the triangle should be, the object came out curved and oblique.
- Does Alyoshenka go to the meeting room? - Lenochkina stuck in the door interested face.
- Alyoshenka is not attending the meeting. Aleshenka writes an article.
- About the cubes?
- What other cubes? - I lowered my eyes, in my hands and the truth was an unfortunate cube. That is a ball. That is a rhombus.
- Not about cubes! And not about balls. About templates.
“I'll tell them that!” Pattern, ah. - Helen was already running down the corridor.
"About templates. Even about three different templates." More precisely, about three reasons to use templates in server code. And none of these reasons will be about HTML.
In the examples, I used Mustache syntax , due to the laconic syntax and the presence of implementations for everything that moves. Mustache practically does not allow itself any liberties, unlike, for example, .Net Razor, which allows you to code inside a template, thereby setting a bad example for weak developers.
Code examples will be in python. The implementation of Mustache for Python is called pystache .
So, three reasons to let patterns into own life your code.
Text artifacts
If you have a system inside which some data exists - for example, data in a relational database or data obtained through API calls, sometimes you need to create artifacts based on this data.
The artifact can be, for example, JSON or plain text file, attachment, HTTP response. The main thing - an artifact is essentially the result of applying a function from some relatively compact part of the data in your system. And the artifact has its own syntax.
The artifact may be a bank statement in text format for upload to the legacy system. The artifact may be the unloading of an electronic check in the form of a .json file, which will be sent as an attachment to the client by mail.
In all these cases, you will greatly simplify your life by using artifact templates.
What is a template engine? This is a library that will take an object model (context), take a template, apply one to another - and give the result. The object model and template are prepared by the programmer. The final result is prepared by the template engine.
Example: try to create a text message about the order.
First, prepare the object model:
def add_comma(list):
for a in list[:-1]:
a["comma"] = True
def get_model():
res = {
"documentId": 3323223,
"checkDate": "01.02.2019 22:20",
"posId": 34399,
"posAddr": "Urupinsk, 1 Maya 1",
"lines": [
{
"no": 1,
"itemtext": "Hello Kitty",
"amount": 3,
"sumRub": "55.20"
},
{
"no": 2,
"itemtext": "Paper pokemons",
"amount": 1,
"sumRub": "1230.00"
},
{
"no": 2,
"itemtext": "Book of Mustache",
"amount": 1,
"sumRub": "1000.00"
}
],
"total": {
"amount": "3285.20"
}
}
add_comma(res["lines"])
res["posInUrupinsk"] = res["posId"] > 34000
return res
The code is purely dummy. In real code, there may be a database query, some kind of logic for calculating values (for example, we calculate the value of total.amount based on order items).
Pay attention to a few things:
- This is NOT an object model of the order, it is something simpler, prepared for use in the template. The values "sumRub" and "total.amount" in the real business model should not be textual, the "comma" value of the lines array in the object model does not belong, it is only needed to simplify rendering (pystache cannot understand itself that the list item is the last and after it there is no need to put a comma.
- The field type "amount" is the text and this text is formatted for output in the template. If your template engine supports formatters (something like "... {someValue | asMoney}"), then formatting directly in the model is not necessary.
- The text in the template should look slightly different for clients from Uryupinsk (the manager ran up at the last moment and asked to add - the business really asked, they suddenly launched a marketing campaign for the city). Therefore, we added the boolean value "posInUrupinsk" to the model and used it in the template.
- It’s better not to use the model again from the template, except for rendering other templates
The text of the mustache template looks like this:
{{#posInUrupinsk}}
ДОРОГОЙ КЛИЕНТ ИЗ УРЮПИНСКА! ОТПРАВЬТЕ НОМЕР СВОЕГО ЧЕКА НА
КОРОТКИЙ НОМЕР 100 И ПОЛУЧИТЕ КОФЕВАРКУ.
{{/posInUrupinsk}}
{{^posInUrupinsk}}
ИНФОРМАЦИЯ О ВАШИХ ПОКУПКАХ:
{{/posInUrupinsk}}
{{#lines}}
#{{no}} ... {{itemtext}}: {{sumRub}} руб{{#comma}};{{/comma}}{{^comma}}.{{/comma}}
{{/lines}}
ИТОГО: {{total.amount}}
---------------------------
N документа: {{documentId}} от {{checkDate}}
We see in the template that the document header for orders in Uryupinsk is different from other cities. We also see that at the end of the last line with the commodity position there is a dot, and in all early positions there is a semicolon. The "comma" attribute and the "add_comma" method in the model generator are responsible for this.
The code that applies the context to the template is trivial:
model = get_model()
with open(os.path.join("templates", "check.txt.mustache"), 'r') as f:
template = f.read()
check_text = pystache.render(template, model)
print(check_text)
Result:
ДОРОГОЙ КЛИЕНТ ИЗ УРЮПИНСКА! ОТПРАВЬТЕ НОМЕР СВОЕГО ЧЕКА НА
КОРОТКИЙ НОМЕР 100 И ПОЛУЧИТЕ КОФЕВАРКУ.
#1 ... Hello Kitty: 55.20 руб;
#2 ... Paper pokemons: 1230.00 руб;
#2 ... Book of Mustache: 1000.00 руб.
ИТОГО: 3285.20
---------------------------
N документа: 3323223 от 01.02.2019 22:20
Another tip: if the task allows it, save the model itself with the rendered template (for example, in JSON format). This will help with debugging and troubleshooting.
The printer squeaked three times, issuing a new model. The triangular side was now a perfect triangle. The other two sides were square. The neural network lived its own life and refused to give out a model model primitive in all respects.
"I'll give Helen a cube." I thought. Let it rejoice.
Code Generation
You may need to create JavaScript in runtime from inside the backend. What for? For creating browser-side reports, for example. Or get a script in F # from within a Go program. Or the Kotlin code from within ReactJS (I can’t imagine why this might be necessary, but suddenly you have such specific inclinations).
In the case of code generation, it is better to first write with your hands the resulting code (what we want to generate) and only then break it into a template and model. This approach will save us fromlongingexcessive complexity of the model. It's never too late to complicate a model, but it's best to start with a simple one.
var report = CreateChart({ title: "За второе полугодие - сводный" }, type: "lineChart", sourceUrl: "/reports/data/0" );
report.addLine({ dataIndex:0, title: "Доходы", color: "green" });
report.addLine({ dataIndex:1, title: "Убытки", color: "red" });
report.render($("#reportPlaceholder1"));
var report = CreateChart({ title: "За второе полугодие - по продуктам" }, type: "lineChart", sourceUrl: "/reports/data/1");
report.addLine({ dataIndex:0, title: "Hello Kitty", color: "#000" });
report.addLine({ dataIndex:1, title: "PokemonGo", color: "#222" });
report.addLine({ dataIndex:2, title: "Mustache", color: "#333" });
report.render($("#reportPlaceholder2"));
Here we see that we have from one to N lineChart charts on the page, each of which has its own data source, title and list of indicators. Modelka:
def get_model():
return {
"charts": [
{
"divId": "#reportPlaceholder1",
"title": "За второе полугодие - сводный",
"sourceUrl": "/reports/data/0",
"series": [
{"dataIndex": 0, "title": "Доходы", "color": "green"},
{"dataIndex": 1, "title": "Расходы", "color": "red"},
]
},
{
"divId": "#reportPlaceholder2",
"title": "За второе полугодие - по продуктам",
"sourceUrl": "/reports/data/1",
"series": [
{"dataIndex": 0, "title": "Hello Kitty", "color": "#000"},
{"dataIndex": 1, "title": "PokemonGo", "color": "#111"},
{"dataIndex": 2, "title": "Mustache", "color": "#222"},
]
}
]
}
Well, the template:
{{#charts}}
var report = CreateChart({ title: "{{title}}" }, type: "lineChart", sourceUrl: "{{sourceUrl}}" );
{{#series}}
report.addLine({ dataIndex:{{dataIndex}}, title: "{{title}}", color: "{{color}}" });
{{/series}}
report.render($("{{divId}}"));
{{/charts}}
Please note: this "head-on" approach to standardization requires a separate effort to screen values in the model. if a comma or quotation mark “Hello Kitty \” ”sneaks up in series [0] .title, the syntax of the resulting file will fall apart. Therefore, write screening functions and use them when creating models. Use formatters if the template engine can do this.
The third die flew through the door and bounced with a knock. It’s also no good. Interesting. but can you roll the cube so that it slips under the door and reaches the end of the corridor? Is it possible to 3D-print rubber? Or is it better to make it filled with such small icosahedrons filled with air? ...
SQL queries
The picky reader will say that this is also code generation, translation of concepts from one programming language to another. To which we will answer the picky reader that working with SQL or with any other database query language is a slightly separate programming area and it is not obvious to everyone that if js scripts can be generated by templates, then SQL is also possible. Therefore, we will issue requests in a separate case.
And so that the picky reader does not get bored, we will leave in the article only examples of a few queries. You yourself can figure out which model and which template are better suited here. In the examples on the github you can see what I did.
SELECT * FROM hosts WHERE firmware_id=1 AND bmc_login='admin' ORDER BY ip DESC;
SELECT * FROM hosts ORDER BY name LIMIT 3;
SELECT host_type_id, COUNT(*) FROM hosts GROUP BY host_type_id;
Templates (including for SQL) and code examples can be found on the github .