Skip to content

A description and example UWP implementation for a highly composable app architecture

License

Notifications You must be signed in to change notification settings

futurice/composable-app-architecture

Repository files navigation

!EVERYTHING IN THIS REPOSITORY IS HIGHLY WIP!

Composable app architechture

The idea of the architecture proposed here is that 99.9% of a codebase should be decoupled components that do nothing at all on their own. The components, which represent different layers of the software structure (eg. data, business, and presentation-layers) are then composed into a functional software in a single composer. The composer is unapologetically cross-layer and cross-concern, but maintains an appropriately abstracted view of the codebase by acting on the public APIs of the aforementioned components. It is also very (if not completely) declarative and handles injecting any dependencies and connections to and between the components.

The goal of the composer is not to abstract away the implementation and read like a domain specific language, but to bring the usage of the different components in the app front and center. Looking at it, you should be able to tell the whole general runtime structure of the app and see all the cross-component dependencies that are in place. Having the the composition of the components and their public APIs readily accessible in the same scope makes many even very large modifications easy, increases component reuse possibilities, and removes the need for building long pass-through chains of parameters throughout the software layers.

As all the dependencies between components have to be explicitly defined in the composer, it might be an unpractical approach for components that depend practically on every instance of a specific component, or even every instance of multiple types of components. To complement the composer in these cases an extension system is suggested.

The core of the system is an ExtensionsContainer which resembles a dependency injection container. However, Instead of the DIs approach to inject services (as interfaces) into the components, this system builds around ‘micro interfaces’ that act as ‘extension slots’ into the components. The extensions then tap into these extension slots by implementing a specific set of interfaces necessary for their functionality. The interfaces themselves are highly app specific and often contain only one or two methods that the relevant components call on the extensions instances. In practice the components get the extension instances implementing the extension interface(s) for the component from the ExtensionsContainer and then call the method(s) of that interface with appropriate parameters. To implement cross-cutting features, such as analytics, it is common for an extension to implement several of these interfaces and then manage it’s internal state in reaction to the calls for the methods defined by each ‘extension slot’ interface.

The system makes it possible to implement features such as ads, paywall, analytics, and push notifications without rest of the codebase ever being aware of such things (or even about an IPaywallService etc). The components just need to expose the aforementioned extension slots for the extensions to be able to implement their functionality. Examples of these extension slot interfaces include: IOnNavigatedToHandler, IOnNavigatedFromHandler, IViewChangedHandler, IAppSuspensionListener, IAppResumeListener, and IViewModelVisibilityListener. Some slots might pass no parameters and expect no return values, some might pass parameters, and some might handle an optional return value letting components to (for example) inject UI into the component or to change the control flow.

The extension container offers the extenders different strategies for handling cases where multiple extensions tap into the same extension slot. For example, in some cases all of the bindings to a slot might be invoked, in some cases it might only make sense to run them until one of them returns a specific value, in some cases they could be run asynchronously in parallel and the results handled afterwards. Additionally the extension container could keep track of exceptions thrown by the extensions and disable a non-vital extension (such as ads) that has clearly ended up into a state in which it can not anymore function correctly. Depending on the implementation of the extension, the container could possible also ‘restart’ the extension to return it into a valid state. This way bugs in non-vital functionality, or in 3rd party libraries they depend on wouldn’t take the whole app down.

Most extensions end up being a single class with all of the functionality for that extension being completely self contained. The system also makes it possible to remove functionality from the app by simple not adding the specific extension to the app’s extension container.

About

A description and example UWP implementation for a highly composable app architecture

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages