Architecture Note
Design Architect Notes
Design Architect Notes
- Domain objects
- They should be Serializable / can be preserved into database
- They are not aware of container (easy to test)
- So they do not fire events announcing its state change
- although it's temping to let domain object announces its own state change centrally (so controller would not forget to announce the event), this is bad idea because
- to fire event, domain object needs to be injected with Event, and this makes domain object a bean, which depends on container, and .... what a mess
- in one controller request a state can be updated multiple times, we do not want to fire multiple events for this - we want just one
- so unless I can think of another "smart" way, do not fire state change event automatically
- although it's temping to let domain object announces its own state change centrally (so controller would not forget to announce the event), this is bad idea because
- Session
- @SessionScoped - of course
- Session object - anything that is attached with a session, and wish NOT be automatically instantiated
- @SessionScoped objects - object that is attached with a session and needs to be automatically instantiated
- Views
- @Tag(ViewCat.VIEW_NAME) if
- multiple implementation of the same interface is needed (most case not needed)
- qualify for easy injection (embed), and / or
- @CDIView(ViewCat.VIEW_NAME) - for navigation, or
- NO qualifier - user will inject the specific type
- @SessionScoped: DO NOT USE!! NOT WORKING WELL WITH Vaadin Framework!! (see https://vaadin.com/docs/-/part/framework/advanced/advanced-cdi.html)
- @UIScoped (non-proxy version) If view is "one per user screen" - note this may crosses vaadin user session
- have a default constructor, which
- can construct "fixtures" i.e. labels & forms but without content
- no necessarily do anything (if it depends on injection)
- @PostConstruct initialize() initialize after fully constructed. Calls method updateContent()
- (private unless necessary) updateContent()
- the session scoped view depends on session state in session object or other @SessionScoped object;
- updateContent() is called by initialize(), and by event observers
- public void handler(@Observes StateChangeEvent event) and updateContent() (it's the right instance on screen)
- have a default constructor, which
- If a view is dependent on nested object states (multiple instance in same user session, usually commonly rooted - like one tree has multiple leaves); they also requires managed beans (such as user session) but also needs to be initialized with its own (individual) object state
- Solution 1: CDI
- @Dependent scope
- parent view use Instance<T> to get new instances (cannot new because the child need to be managed by container)
- T initialize(StateObject state) : parent provides the state object to the obtained instance with a method; return "this" for convenience
- no need to be serilizable
- no need for default constructor, therefore an injection constructor can be used
- it can still handles its own clicks (all views can) use lambda or anonymous inner class
- do not @Observes event and update "this", because it's a different "this"
- ok to use member variable in lambda / anonymous class
- do @Observes event and update the instance from creator, who holds an instance of the view
- feel free to use outer variable (not only outer class member) in inner anonymous / lambda, cause (read this) value (for object, reference) of the outer variable is copied into inner closure even if, shown in below example (it works), the variable is a loop variable. note if I add "child = null" after the adding of click listener it would not work, again as explained in the link (do read that)
- Solution 1: CDI
- @Tag(ViewCat.VIEW_NAME) if
// handles children
for (Claim child : children) {
ChildrenClaimsGrid childGrid = childGridInstance.get().initialize(child);
Button claimNumberButton = new Button(String.valueOf(child.getNumber()));
claimNumberButton.setData(child);
claimNumberButton.addClickListener(button -> {
// controller.onSelectFocusedClaim((Claim) button.getButton().getData());;
controller.onSelectFocusedClaim(child);
});
addComponent(claimNumberButton,
0, childrenStartsFrom, 0, childrenStartsFrom);
addComponent(childGrid,
1, childrenStartsFrom, 1, childrenStartsFrom);
childrenStartsFrom++;
}
return this;
- Solution 2: non-CDI
- just normal, non-managed object
- for managed beans, pass from user (container view object) which could be managed
- still, updated by "parent" view
- good: clear; bad: needs to pass everything from parameter
- Solution 3: non-CDI, inner class (not static inner)
- inner (not static) class depends on an instance of outer class
- so no need to pass outer objects around
- Events
- for menu item, use the general ActionTriggeredEvent(my own code) with tag
- for "click" "event" (because here we are not talking about CDI event), handle locally
- after click event handled (by calling domain objects), fire new StateChange event
- @SessionScoped bean @Observes state change event and update itself
- @Dependent scoped bean should not @Observes state change event and update "this", but the "depended" object should update the instance held by it
- In "xxxlistener" method handling UI click / select etc. event (not CDI event), the code should not contain very much domain logic, all domain logic should go to domain objects or session
- Navigation
- @SessionScopped Outer view @Observes event and changes inner view