In this chapter, we will introduce the best practice of Asta4D from our own experiences. There are some conventions and assumption in this chapter, absolutely there are always special cases which are out of our assumption, however you can break any rules to handle your special cases, we only discuss the common cases in this chapter.
Table of Contents
MUST do all the operations with side effect at request handler side, that is so we called isolating side effect.
NEVER do any operations with side effect at snippet side.
Table of Contents
It is a little bit difficult to explain this rule. For a page, we assume that there are always only 2 types of pages. Let us see how we should divide our logic between the request handler and snippet.
request handler
Retrieve the entity(resource) from back-end storage(usually the db) by given id which is specified by the URL
Decide how/what to response
(for example)If the target entity does not exist, return a 404 to the client
(for example)If the target entity has been changed/migrated to another entity, return a 301 to the client
(usually)Save the entity to Context and then forward to the target html template
snippet
Retrieve the saved entity(resource) from Context(by @ContextData)
Perform other necessary query for rendering
request handler
For simple cases, a request handler is not necessary
Because the snippet can retrieve the condition by @ContextData directly. However we recommend to declare a POJO to represent your search condition for a little bit complicated situations.
Example 9.1. Snippet handling a search condition POJO
@ContextDataSet public class Condition{ @QueryParam private String queryword; public String getQueryword(){ return queryword; } }
public class MySnippet{ @ContextData private Condition condition; }
Normalize the search condition POJO
In some complicated situations, you may need to retrieve the POJO in request handler then do some necessary normalization. After the POJO was normalized, you should save it into Context to pass to snippet.
Example 9.2. Normalize the search condition POJO
@ContextDataSet(singletonInContext=true) public class MyHandler{ public static final String SEARCH_CONDITION = "SEARCH_CONDITION#MyHandler"; @RequestHandler public void handle(Condition condition){ //do some normalization here ... //This is not necessary //Context.getCurrentThreadContext().setData(SEARCH_CONDITION, condition); } }
public class MySnippet{ @ContextData private Condition condition; }There is a trick that if we define a ContextDataSet's singletonInContext to true, there will be only on single instance in the context, thus we do not need to declare the name of ContextData even we stored the POJO by name at the handler side. Even that you do not need to save the condition POJO explicitly since it is the single instance in the Context.
snippet
Retrieve the saved search condition POJO from Context(by @ContextData) or simply declare the search condition as its own fields(by @ContextData)
Perform the query by given search condition to retrieve the list of entities(resources)
Perform other necessary query for rendering
Table of Contents
We encourage developers to retrieve the data at the place where the data is required rather than retrieve a complicated data structure which holds all the necessary data for the whole page rendering in bulk. We make a convention that there are only 4 type of return value in our service layer(where the queries are performed exactly):
a single entity which is usually the POJO for ORMAP
an list of entities' ids(not the entities themselves)
a count of some aggregation queries
a map as <id, count> of some aggregation queries
In the implementation of render methods, we always retrieve the entity instance one by one even we are rendering a list of entity (exactly we only have a list of entities' ids). For the concern about performance, see the later description of cache. However, there must be some situations that we cannot be satisfied with the performance by retrieving data in a too small granularity. We handle these cases as following steps:
Ask yourself, is it true that the granularity of data retrieving is the bottleneck of current performance issue.
Ask your team members to confirm the bottleneck again.
Implement a query method which returns a map as <id, entity> in bulk, then retrieve the entity from the map in the list rendering.
If there is still a performance issue, ask yourself and ask your team members to confirm the bottleneck again.
Build a query to retrieve all necessary data in bulk then return a complicated data structure from the query method just like what we are used to do in the traditional MVC mode.
In fact, the complicated data structures are rare in our system and most of our cases can be satisfied with the basic ORMAP entities.
The ParallelRowRender splits the rendering action to threads with the size of rendered list. If there are some heavy operations in the list rendering, ParallelRowRender is a good option to reduce the time of rendering.
Since we are encouraging developers to retrieve data by a small granularity, the cache is very important to the system. Especially we often retrieve entities one by one in the list rendering, the cache of entities is the most important thing for performance. As a matter of fact, we can use the cache more effective because of the small data granularity.
An frequently asked question is how to avoid duplicated query in the snippet class. That is actually an problem since we encourage the developers perform queries at the place where the data is required. We have the following options to combat this issue:
Global query lock via AOP
This is the first option that we recommend for query heavy systems and exactly what we did in our system before we moved to saas architecture. We split all the real queries to a service layer then perform a global lock for all the equal calling parameters on the same method. We use spring as our IOC container and simply add the lock logic by the spring's AOP mechanism.
ContextBindData
For the lighter systems, ContextBindData would be a good choice for most situations. The get method of ContextBindData will be executed only once in the current Context. See the detail of ContextBindData at Section 12.2.3, “ContextDataFinder”.
ContextBindData can also be used as a graceful simple wrapping mechanism for data retrieving at snippet layer. Developers can declare the common queries of a snippet class as ContextBindData fields of it.
InitializableSnippet
A snippt class can also implement an interface called InitializableSnippet. The init() method of a snippet which implements the InitializableSnippet will be called once after the snippet instance is created. Developer can do some prepare work for rendering by this mechanism such as performing common queries. However, performing query in init() method is not recommended but everything is by your choice.
In multiple thread rendering, the snippet classes may still be instantiated multiple times. There is no warranty about it.
As we mentioned above, InitializableSnippet can be used to do some initialization work after the snippet instance is created. We do not recommend to perform common queries in init() method of InitializableSnippet, but the InitializableSnippet interface is still a good option for preparing a snippet instance.
The classical usage of InitializableSnippet is that we can set default value of declared @ContextData fields if some of them are set to null.
Though we can use various selectors to render data into the template file, we recommend to use css class name selector in common cases, which affords the best compatibility to the frond-end refactoring. We also made a convention which is called "x-" convention. we will always add a css class started with "x-" to the data rendering target, which tells the front-end guys try their best to keep the compatibility against the "x-" marked elements when they are refactoring the html sources.
Although we always use "x-" convention to render our data, there are still some acceptable exceptions in our practice:
use "a" directly when rendering the link to the "href" attribute
use "img" directly when rendering the link to the "src" attribute
use "li" directly when rendering a list
This one is controversial, some of our members argue that we should use "x-" instead of direct "li" selector.
Sometimes we need to do conditional rendering. For instance, we assume that there are two tabs which can be switched by different query parameter, a possible way is to create the target tab contents dynamically at snippet side as following:
Example 9.3. create contents dynamically
public Renderer render(int id){ Element elem = null; if(id == 0){ elem = ElementUtil.ParseAsSingle("<div>" + id + "</div>"); }else{ elem = ElementUtil.ParseAsSingle("<div class="great">" + id + "</div>"); } return Renderer.create(".x-target", elem); }
Apparently there is bad smell in above example, we generate DOM element at snippet side, which makes front-end refactoring difficult. The recommended way is to write all the possible contents in template file and remove the unnecessary ones in snippet.
Example 9.4. remove redundant contents
public Renderer render(int id){ Renderer renderer = Renderer.create(); //remove the clear mark renderer.add(".x-target-" + id, "-class", "x-clear"); //then clear the left ones which is not necessary renderer.add(".x-clear", Clear); return renderer; }
<div class="x-target-0 x-clear">0<div> <div class="x-target-1 x-clear">1<div>