Blueprints
(draft)
Here are some guidelines on how to build a webapp with ztemplates.
After choosing a framework you have to find out how to use it best. I found the following architecture to work well.
The proposed architecture aims to reduce dependencies and to create selfcontained components. It leads to lots of immutable, dumb objects. Use your IDE code generator for that.
The single most important thing to keep in mind is to keep the view pojos completely dumb and passive.
View layer
The view Layer only uses objects from the ztemplates.render package and has no dependency on other layers of your application.
It can also use the ZTemplates.getRenderService() for rendering components and creating unique javascript ids.
A view consists of the following:
The view template
Responsibilities:
- contains the markup and layout
Implementation:
- typically velocity or freemarker template or JSP
- naming convention: same package and name as view pojo but with vm(velocity), depends on the Renderer used
- package name proposal: com.company.yourProjectName.web.views.yourViewName
- uses only data exposed by the view pojo
- TIP: it's very useful to tag the container markup created by a template with class="${cssId}". When using a tool like firebug
you will find the corresponding template very quickly when inspecting the generated markup.
The view pojo
Responsibilities:
- exposes values to the view template
- declares needed css and javascript
- instantiates nested view pojos using the data from the view data object
Implementation:
- package name proposal: com.company.project.web.views.usecase
- name convention: XxxView or XxxPanel
- gets its data in the constructor
- declares only getters without any logic in them
- declares all variables as private final, so they must be initialized in the constructor
- normally no methods need to be called on the view pojo by your application code
- highly configurable components may have methods that can be called form other view pojo objects, but try to avoid this.
- try to create a different sub-package for every view-pojo. Place all related files into it.
So the pattern is: private final variables, simple getters annotated with @ZExpose, initialisation in the constructor with data passed as parameter, no logic except calls to ZTemplates.getRenderService().createJavaScriptId();
The view data object(s)
This one is not really mandatory. Alternatively you could pass the data one by one to the view pojo. But if you need to build complex views this comes handy as you can create factories for the view data objects.
Responsibilities:
- provides all data needed by the view pojo, including urls. The view pojo can be fully instantiated with the data from the view data object.
Implementation:
- immutable
- all members are private final
- all data is passed to the constructor, including collections
- no setters, only getters
- is passed in the constructor to the view pojo
- same package as the view pojo
- name convention: same package as view pojo, name is view pojo name + suffix "Data"
- also define all needed data objects like rows in a table or the like in this package
- package name proposal: com.company.project.web.views.usecase
- name convention proposal: XxxData
Action layer
Responsibilities:
- The active part of a ztemplates application.
- Handles the http requests.
Implementation:
- action pojos that are mapped to urls.
The action pojo
Responsibilities:
- verifies url parameters
- process parameters, possibly by accessing the service layer
- decide which view to display, depending on parameters and processing outcome
- page-flow: decide when to redirect to other action-pojos with sendRedirect
- accesses the service layer to get service layer data transfer objects (DTOs)
- creates view data objects from DTOs
- creates URLs to other action-pojos and places them into view data objects
- creates view pojos, passes view data to them
- renders the view pojos to the response
Implementation:
- name convention proposal : XxxAction
- package name proposal: com.company.project.web.actions.usecase
- place actions into package separate from views (test: could you compile views without actions?)
- create a static String createUrl(param...) method, that creates a url to the action (by using ZTemplates.getServletService().createUrl(pojo)). Use this whenever you need a url to the action, so you are refactoring safe.
- define a default constructor and make it protected. The only place that needs to instantiate a action pojo is the createUrl() method.
- get all service layer DTOs in one request to the service layer. This ensures the data is consistent as it is read in a single transaction. This implies you need a specialized service layer call for every action.
Service Layer
Responsibilities:
- provides immutable, customized, aggregated and preprocessed data (DTOs) from the persistence layer to the action layer
- provides customized methods for certain actions
Implementation:
- name convention proposal : XxxService
- package name proposal: choose your own
- implemented as session-beans (stateless whenever possible)
- try to not reuse methods in different actions
- only pass immutable DTOs to and from the action layer, NEVER entities, even when detached (reference loading problem).
- I found the following request/response pattern to work well in projects:
public XxxRespDTO xxx(XxxReqDTO req);
//Example:
public interface CustomerService {
public CreateCustomerRespDTO createCustomer(CreateCustomerReqDTO req);
public UpdateCustomerRespDTO updateCustomer(UpdateCustomerReqDTO req);
public DeleteCustomerRespDTO deleteCustomer(DeleteCustomerReqDTO req);
}
- If you feel the XxxReqDTO is overkill, pass the parameters one by one, decide by yourself. I feel it is easier to maintain if you use the XxxReqDTO, becaus the service interface signature does not have to be updated if you add/remove parameters. Also you can introduce base-classes. It also resembles the RPC pattern that is proven to work well. Also maps naturally to XML over HTTP or webservice calls.
- I found it useful for the XxxRespDTO to carry a enum field ReturnCode, so you can accurately report error conditions to the caller. Define a new enum for each response DTO, so you don't get unwanted dependencies or side effects.
- create specialized, immutable Req/Resp DTO's for each request to the service layer. If you want to reuse code, create reusable DTO's (like the Mirror DTO, see below) in another package and put a instance into your specialized ones.
- The XxxRespDTO's should only be used as parameters for the service call they were created for, don't reuse!
Mirror DTO's
If you use entity EJB's you may find it helpful to create a set of mirror DTO's (immutable, only getters) that mirror the entities, but
without references, so you can comfortably add a general mirror DTO to a specialized response XxxRespDTO. You can reuse the mirror DTO's in different usecases.
The mirror DTO's can be seen as a kind of immutable detached entities without references.
AJAX/JavaScript enabled components
(follows)
new features in 0.9.9.7