The current CEF front-end (referred to generically as CEF in this
document) is showing its age in both time and technical debt. In order
to increase flexibility and reduce development time a re-write of the
codebase offers the best path for improvement. The plan outlined in this
document is based on three years of implementing, customizing,
supporting, and maintaining the current codebase.
The front-end part of the CEF application is the user interface and all
the code that supports building and deployment. It is primarily composed
of HTML, CSS, and JavaScript that renders in a web browser. That web
browser could be running on any number of platforms with their own
unique requirements though. Taking a step back and looking at the larger
industry, targeting only a web browser is short-sighted. Beyond desktop
and mobile browsers loading web pages, there are alternate forms of
delivery like progressive web applications, hybrid applications, and
even the possibility of natively compiled applications that should be
considered.
CEF is an application that is built on the Angular JavaScript framework.
Frameworks provide a tested and opinionated base of low-level
functionality to build on. Beyond convenience for the initial
development, it also aids maintenance by building on the work of
dedicated specialists, and can increase client confidence because their
developers will have a common entry point for understanding the code.
The new CEF will use the updated Angular 2 framework. This new version
is a major rewrite that refines the core concepts of original Angular,
as well as pulling in new lessons learned from other libraries like
ReactJs. Angular 2 itself is built on several foundational libraries,
and has grown to include an ecosystem of assistive tools.
Here is a list that highlights important technologies that are expected
to be used for the new CEF:
Notable improvements to the development process come from leveraging the
Angular-cli project. It allows a development process where changes are
saved and then immediately compiled and reloaded in the browser. This is
done with a web server running locally. Unlike current development, a
full installation of a production-ready web server, database, and CMS
will not be required.
A suggested change to the development process is to have back-end
developers maintain an up-to-date instance of the CEF back-end on a
central server. This should decrease the time front-end developers spend
trying to get service endpoints operational locally. This becomes
practical because the new application will have much better
configuration management.
A major architectural goal is to increase the flexibility and modularity
of the application. To that end the core of CEF will be separate from a
client implementation.
In Angular2, applications are composed from a tree of nested components,
each of which are the combination of a template and the logic to support
rendering a small piece of the view. These components will be "dumb",
and depend on business logic from an Angular2 services layer (based on
RxJs) that manages application state and communicates with the back-end
web services. Angular 2 also encourages packaging related functionality
into code modules, which will be utilized heavily. Core CEF will be a
component module and not a functional site (There will be a reference
site implementation).
A client implementation will be a separate Angular 2 application that
depends on the core CEF module. All configuration, customization, and
styling for a site will be part of the client application. Angular 2
provides new ways to override and extend base functions so that the core
can stay unmodified. As sites are completed, useful additions to the
platform can be pulled into the core, or exported as reusable modules
for narrower use cases.
This section aims to lay out the general architecture of the many
modules that will eventually comprise CEF Core. The back-end API is
already grouped by areas of similar functionality and the front-end will
aim to follow that convention when possible.
Angular 2 modules are containers for code components (even other
modules) that make it easier to define, move, and modify pieces of the
application. Modules expose a public API to the module importer and can
be used for configuration of the dependency injector, which is a
critical component of Angular 2 and must be understood to fully realize
an application’s potential.
An Angular 2 application is best visualized as a tree of nested units of
code, usually in the form of view-centric components. This tree is also
the basis of the hierarchical dependency injector, which is responsible
for managing the allocation of other units of code that are most often
business-logic-centric Angular services. If a component requests
something from the injector, the tree will be traversed from the
component’s location up, until an existing instance of the requested
dependency is found. Alternately, a component can request a new instance
of a dependency. This management separates the origin of a code unit
from the consumer of the requested code.
A good example is the use of a CartService, which is responsible for
managing the contents of an ecommerce shopping cart. One instance of
CartService is created at the root of the application. Somewhere down
the tree, there is a button component that will add an item to the cart.
This button component has requested an instance of CartService from the
injector. Finding no other instances of the CartService along the path
to the root component, the injector returns the CartService that was
previously created at the root.
Elsewhere in the application tree, there is a WishList component, which
is a specialized version of a shopping cart. The WishList requests a
fresh instance of CartService because it does not want the shared
shopping cart for the site. Both the new CartService for the WishList
and the CartService at the root are identical pieces of code, just
individual instances.
The other important facet of dependency injection is the ability to
define what should be returned for any given request. Modified versions
of services can be substituted for the original via application
configuration. This is key for understanding how the client application
can be customized without modifying CEF Core.
Continuing with the WishList, in this application some extra code unique
to the WishList needs to be added. From the client application, the base
class for the CartService can be extended. We’ll call it
WishListService. Now, in the application configuration the dependency
injector can be told to return the custom WishListService whenever
CartService is requested. As long as the alternate code conforms to the
expected API, the component is unaware that CartService is different.
The example is simplified, but hopefully outlines a complex concept.
Angular services are focused on managing server communication, business
logic, and application state. They do not do rendering or anything
directly visible. A hypothetical CartService manages the shopping cart;
keeping the current item list. It takes requests from application
components to add or remove items to the cart, syncs the information
with the server, and reflects the state change back to any components
that are using CartService as their source of data.
Services should be focused on a single domain of functionality whenever
possible. Services can inject and use other services when needed, and it
will be best-practice to keep any server interaction in a separate
service. This way, the data communication can be replaced for different
environments or mocked for testing and development.
In Angular 2 a component is a controller class that is paired with a
view template. Components are responsible for rendering what the user
sees, managing user interactions, and facilitating input to the logic
services. They should never directly manipulate application state. CEF
Core will supply a full set of base components that can be used to
create a fully functional ecommerce site.
Components should be as narrow in focus as possible since they often
manage complex user interactions. Angular components support inputs and
outputs for linking them together. One component may manage the layout
of a section while another may only handle selection from a dropdown
list. The goal is to have a reusable collection of parts that are not
overly abstracted.
All assembly and customization of a storefront will be done in the
client application. When starting a new site, a reference implementation
will be cloned and modified as needed. This allows for all client
changes to be versioned and managed in one place. If a particular
enhancement or new component is a good fit for CEF Core it can then be
integrated and fully tested.
Many options fall under the category of configuration, but it will
encompass things such as endpoint sources, base paths for images,
application variables, language and currency display, etc. As noted
before, module imports and dependency injection are also managed through
an Angular application configuration.
Beyond Angular, configuration will also cover build and deployment
options such as minification, library bundling, pre-compilation, and
rendering environment. Most of this work will be done via Angular-cli
and Webpack.
Application routes map a URL to a component instance, and are initially
established at the root level. Components are rendered to viewports,
which are DOM containers. When using CEF with a CMS, we should only need
to add a base viewport to the skin, or provide a viewport widget.
The power of routes is that the URL structure of a site can be managed
separately from the application code. A route could use “/catalog”,
“/products”, or “/stuff”, but they would all point to the same
CatalogComponent. Routes can also be used for persisting application
state through URLs so a copy-paste to a different browser would produce
the same result.
All modifications to view templates will be kept with the client
application. A mechanism will ensure that local templates can override
the default templates. When an individual component needs a template
that is different from the application default, an additional component
can be declared that is essentially the same except for the template
(and/or CSS) path. A final method for managing the merging of template
data has not been chosen, but the most promising options are a custom
Typescript decorator, or file-swapping during the build process similar
to how it is currently done.
All components will have a fully-implemented Bootstrap template. This
provides a consistent base for UI testing, demonstration, and styling of
new storefronts. Since this plan is forward-looking Bootstrap4 will be
used. If we design sites within the confines of Bootstrap's framework,
much of the styling will be accomplished by modifying the bootstrap
theme variables. Should a component require more extensive styling,
Angular provides a mechanism to attach CSS that can either use global or
isolated style rules.