Best practices
It's a common and well-known practice in software design to do recurring tasks in a similar fashion. This helps to read and understand the code quickly and reliably. As Snippetory works a little different to other technologies, I think it might help to have some additional conventions.
Variable naming
When binding a business object to a template you have typically a sub-template for this business object. I.e. you have to deal with an object that holds the data and an object of type Template to sink the data provided. Thus, the naming should express the relationship of those references and has to distinguish between the different tasks. I recommend doing this by appending 'Tpl' to the name of the business object.
productTpl.set("name", product.getName());
With name of the business object I refer to the real world name, not to the name of the field. In turn other suffixes are skipped, of course.
productTpl.set("name", productDto.getName());
On the other hand abbreviation rules are not affected.
prodTpl.set("name", prod.getName());
Where the relationship to another object doesn't apply, or it's not necessary to express it `template` and 'out' nice options, too.
Method Names
There are three different patterns to be modeled by methods. These patterns are distinguished by the naming.
- getTemplate
-
Acquires a template. Such a method abstracts the source of the template and initializes
the template. To acquire a template it may need to have access to a TemplateContext.
Typically, such a method is used acquire a complete template rather than a sub-template
of a template has already in access.
Those methods typically takes one or more arguments. - render...
- This is the most common type of method. It binds some data to a template. This template is handed over as a method argument. After binding the data the template is rendered. As rendering ends the live cycle of a template, the method typically returns void. However, if necessary it may return some outcome of the rendering logic.
- bind...
-
Sometimes it's not desired to render, but it's necessary to have the option to
build a piece of template by several methods, that can be combined in different
ways. Those methods are bind methods. They create a little more overhead for
their users. In order to keep this overhead as small as possible, they return
the template. This allows chained calls.
public Template bindProductLabels(Template out, ResourceBundle msg) { out.set("name-label", msg.getString("product.name"); out.set("desc-label", msg.getString("product.description"); out.set("price-label", msg.getString("product.price"); return out; } public Template bindProduct(Template prodTpl, Product prod, User user) { prodTpl.set("name", prod.getName()).set("desc", prod.getDescription()); Price p = PriceFactory.getInstance().calculate(prod, user); // Price is rendered using a custom format. Template might look like this: // <td>{v:price price="amount"}</td><td>{v:price price="currency"}</td> prodTpl.set("price", p); return prodTpl; }
Renderers
Template instantiation
Snippetory provides a number of convenient ways to create templates. However, the landscape changed over time. Starting with 0.9.4 I redesigned the API to be clearer structured and more configurable. This creates the need to remove some of those old methods. I already removed some of the more exotic methods, that are simple to replace with remaining ones. Meanwhile, I decided to deprecate entire org.jproggy.snippetory.Repo object. In fact the only ways to instantiate templates are now:- Syntaxes.xx.parse
- As a syntax is mandatory for parsing and a default syntax leads to problems while evolving, all methods depending on the default syntax will be skipped in the future. Hence, this is the only method for ad hoc parsing of strings, that makes sense.
- TemplateContext.getTemplate(String uri)
- The TemplateContext is perfect for maintaining your own standards and configurations. It can be injected, extended, and handled in any way. Building up your own Repo is simple and will definitely better fit your needs. In fact the TemplateContext is the best way for everything, that's too complicated for Syntaxes.parse.
Handling the TemplateContext
The TemplateContext is designed to be lightweight and thus having a low creation overhead. And it`s mutable. This makes caching difficult and not too efficient. The problem with re-use of an instance in a multithreaded environment is, that an instance might be accessible for multiple threads at the same time. And if one of these threads alters the TemplateContext the state gets unpredictable. Thus, it`s a bad idea to configure a TemplateContext as a singleton object in your injection environment.
When using methods to provide a TemplateContext those should create a new instance per call, too. However, it's ok to an instance of TemplateContext for several Template creation one after another.