Architecture Note

Design Architect Notes

  1. 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
  2. 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
  3. 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)
    • 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)
        // 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
  1. 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
  2. Navigation
    • @SessionScopped Outer view @Observes event and changes inner view