> The goal of this template is to remind you of many aspects that you could consider, to help you create a good concept.
> Tip: Use whichever sections are appropriate for the concept, delete the rest, rename and add at will, these are simply a suggestion.
> Tip: Delete all these comments.
Purpose
The Problem
Example Messages
Although messages are a platform thing the UI is heavily involved in displaying, dispatching and storing messages. It is clear that the UI framework should be concerned for the display and visualization of messages and notification but the UI should not be involved for dispatching and especially storing messages.
Here the call from the messages app as an example (apart from the Session all classes are part of ui-framework or message app):
MessagesViewImpl:sendButton → MessagesMainSubApp:handleGlobalMessage → AppInstanceControllerImpl:broadcastMessage → MessagesManagerImpl:broadcastMessage → :sendMessage → (MessageStore:saveMessage → Session.save() ; MessageManager:sendMessageSentEvent)
The reason is simple: Lets say we would like to develop a specific client for mobile devices (either a native app or a specific webapp) then we either had to depend on the vaadin based UI framework or reimplement everything for the other client which would lead to increased maintenance costs and failure rates.
Both MessageManager and MessageStore should be part of the core. Ideally what we dispatch to them is from the beginning a command to notify we want to send out messages. This will both hide JCR from the scene and make messages agnostic to the user may it be admin central, a special native app, a custom JavaScript admin interface, REST, ...
Example Resource Creation
What should happen if a resource is created or overloaded is configured in a UI action. This includes:
- Finding the correct MediaType to set in the JCR properties
- Set the correct metadata in JCR like size, extension, mimetype, etc
- Set the modification date of this node
This functionality can't be reused by other systems other than admin central.
> AKA (Also known as): Starting Position
> Why is this concept being considered? What is the issue that someone is having that needs to be addressed?
Goals
Separation
We aim to separate the the UI from backend operations and both have business logic code separated and are JCR agnostic in the frontend.
Reusability
Business logic like publication, uploading assets, change content (aka create new versions) or restore previous versions should be directly usable by many different users and not just admin central. REST endpoints is a good example or a new or specialized version of the UI.
Responsiveness
Asynchronous behavior, which commands aim for, should be baked into the core and therefore its easier on the front end to provide long running functionality.
Change centric vs node centric
Actions and commands should focus on the full context of a change and not just on one node. Actions become more scalable through this and its easier to apply an action on multiple nodes in one go.
Side effects
The following points are welcome side effects we try to achieve or automatically get but they don't have a main priority atm.
Testability
Through better separation both the business logic and frontend becomes easier to test and ideally many things we currently can only test with UI tests can be tested with just unit tests.
Use Cases
Admin Central
The action "Rename item" gathers the information which should change like: ID of the node to rename, new name, etc
The action handler dispatches the appropriate command with this we leave the user and client context
The central command handler will instantiate the correct command implementation and execute it
The command will do the things it should do. In this case find the correct JCR node and set the new name. Fire events for the changed states.
Command handler will make sure the events are forwarded to the correct users
Action handler will propagate the changes to the data sources
Update on the data source will cause the UI to update (It would be ok or even advisable to already update the UI before: that means the error cases have to be handled more carefully!)
Proposal
Concept
Basically have the split of actions & commands like
Actions are the binding of front end specifics to a command. Actions together with the View Models () encapsulate the full lifecycle of data in the UI and handle both event dispatching and event handler for the data they are concerned. They do not implement business logic (at most they replicate or reuse it) and rely on the backend, through commands, on executing business logic.
Commands are user agnostic and can be used by an actual user interface like Admin Central or by a technical interface like REST or WebSockets. Commands encapsulate the steps behind state changes as well as side effects, like updating modification date or creating a new version, and communicating that changes to others.
Reasoning
> AKA: Rationale
> Pros and Cons
> Consequences of this approach.
Implementation
Command Context
- Calling User
- Locale
With the use of completable future we could use the following pattern in the AddResourceAction e.g:
public class AddResourceAction extends AbstractAddResourceAction<AddResourceActionDefinition> { private final CommandBus cmdBus; @Inject public AddResourceAction(AddResourceActionDefinition definition, Item resourceItem, FormDialogPresenterFactory formDialogPresenterFactory, AppContext appContext, UiContext uiContext, ContentConnector contentConnector, SimpleTranslator i18n, LocationController locationController, ResourceOrigin origin, @Named(AdmincentralEventBus.NAME) EventBus eventBus, CommandBus cmdBus) { super(definition, resourceItem, formDialogPresenterFactory, appContext, uiContext, contentConnector, i18n, locationController, origin, eventBus); this.cmdBus = cmdBus; } /** * Adds a new resource to the {@link info.magnolia.resourceloader.jcr.JcrResourceOrigin}. * */ @Override protected void addResource() { final AddResource newResource = contentConnector.createNewResourceCommand(getParentResource(), getNewResourceItem()); this.cmdBus.dispatchCommand(newResource).thenAccept((e) -> { final DetailLocation detailLocation = new DetailLocation(appContext.getName(), HOTFIX_SUBAPP_NAME, DetailView.ViewType.ADD, newResource.getPath(), null); locationController.goTo(detailLocation); }); } }
Etcetera
> Possible Improvements
> Discarded Proposals
> Discussion
This is related to the following JIRA issues:
DEV-818 - Getting issue details... STATUS
DEV-856 - Getting issue details... STATUS
MGNLUI-3398 - Getting issue details... STATUS
This topic is related to some degree to CQRS. Here some links for interesting read-ups:
- https://martinfowler.com/bliki/CQRS.html
- http://cqrs.nu
- https://docs.axonframework.org/v/3.1/part1/architecture-overview.html