July 10, 2021
This article presents the software architecture of a project generated by an expanded Vue 2 template of the CLI tool. This invocation of CLI includes the Router and Vuex. Several UML diagrams will be presented to show the architecture.
This is a follow-up article to "Default Vue CLI Architecture" which shows the (simpler) architecture generated by accepting the default Vue 2 project.
The architecture to be analyzed is that created by a manually-configured Vue 2 CLI project. This is created using the following command.
$ vue create sample-vuejs-demo-2
Provide the following responses to the CLI screens.
The command generates code, configuration, and build files. The analysis of the architecture begins with looking at the static, structural elements that are realized in the code. With in the src/ directory, there are the following directories.
Within src/, there are the following files
There is a single file in the components/ directory.
There are two file in the views/ directory.
There is one file in the router/ directory.
There is one file in the store/ directory.
In this UML Package Diagram, a Package is represented as a directory. Those files that exist in the top-level src/ directory are given a package "Default". Here is the mapping of directories to Packages.
As stated in the earlier article, Packages group artifacts. In this case, Vue SFCs and other JavaScript files make up the artifacts. The physical implementation of the Package grouping is a directory, though that is not required. This table describes the contents of each package in the generated project.
Package | Description |
---|---|
Default | Single File Components (SFCs) and JavaScript to initialize the app |
Components | SFCs that are entirely functional (no Vuex for example) |
Views | SFCs that have state and map to a Route |
Router | Vue Router definitions |
Store | App-wide Vuex state and related functions |
The most important part of the Package Diagram is the dependencies. Packages usually need other Packages to function properly, but too many dependencies indicate too much coupling. Excessive coupling means that a change in one area can affect another area. Bi-directional dependencies -- the contents of one Package depending on another one and vice-versa -- are to be avoided as are cycles.
The Default Package depends on Router and Store. Code within Default will instantiate Vuex and the Router. Note that the Default Package does not directly depend on Views and Components. Views and Components are volatile Packages that change with each feature or bug.
The Router Package depends on Views. The Router Package contains the Route mappings of paths to imported SFCs. This Package is the mappings only. Views is free to interact with the Router through <router-link> or router.push()
.
The Views Package depends on Components. Components does not depend on Views. Because of the functional requirement imposed upon code in Components, the Components Package has no dependencies. This arrangement is well-known in Single Page Applications (SPA), though it is falling out of favor. See this article as a reason why.
The Store Package is not used yet. However, if it were, Views would depend on it. This future dependency is highlighted with a red dashed arrow. In Vue terms, this means that Views will interact with Vuex but not Components. If Components needs something from Vuex, it will be the responsibility of Views to provided it.
A Class Diagram is used to further analyze the static structure. Each .js file or SFC (.vue) is represented as a UML Class. A UML Class has attributes and operations, but this diagram hides those to emphasize the relationship between the Classes.
The analysis starts with main.js which is the entry point into the application. It will instantiate each of the framework parts: App, Router, Store. This is marked with a dashed arrow indicating a dependency. A UML Standard Profile Stereotype "instantiates" shows the specific dependency.
App, the Router index.js, and the Store index.js interact with the Views. App contains a <router-link> which will cycle through the Views. That is represented as an solid line called an Association. The arrow on the end of the Association is Navigability and it indicates that App knows about Views, but not the other way around. That is, in this design, Views will not attempt to access the parent App object, say through $root. There is a role name "navigates-to" and the arrangement reads "Default::App.vue navigates to Views".
The Router index.js will import or lazy-load Views. This is a one-way Association. The role "imports" is used on the Router side so that the Association reads "Router::index.js imports Views".
Store index.js, is left as-is since it is not in use at the moment.
There are two members of the Views Package: Home.vue and About.vue. On a Class Diagram, a relationship called Generalization allows the members to be treated as a single, notional "AbstractView" class. The Generalization relationship is a solid line with a hollow arrow at the end, pointing to the abstraction. This reads as "Views::Homes.vue is a Views::AbstractView" and also "Views::About.vue a Views:AbstractView".
Abstraction is important when discussing architecture. It reduces the complexity of a diagram by removing similar relationships. This diagram color codes the abstract parts for additional emphasis.
Generalization is also used in the Components Package. "Components::HelloWorld.vue is a Components::AbstractComponent".
While it is Home.vue that includes HelloWorld.vue, the architecture generalizes this into saying the following. An AbstractView (Home.vue) can be composed of an AbstractComponent (HelloWorld.vue). The Association between Views and Components is Composition and is denoted with a solid black diamond on the owning end. This reads "Views::AbstractView may have one or more Components::AbstractComponent". The 0..* is a Multiplicity marking that says "zero-to-many".
The AbstractView and AbstractComponent use italics in the name which is a UML standard. The extra "Abstract" used as a prefix for View and Component can be removed if the team is aware of the style convention.
With Generalization, the diagrams can avoid point-to-point Associations that clutter an architecture diagram. Other diagrams may deal with the specific Class (ex, show Home related to HelloWorld).
With Generalization, the specific Views and Components can be removed from the architecture diagram entirely. The following diagram deals only with AbstractView and AbstractComponent. However, by removing items from the digram, space can be given to show how the Vuex Store might interact with the classes. Rather than snaking an arrowed line by Home and About, the direct Views to Store Association can be made clearly.
But more importantly than reducing the clutter, the abstraction gives the architecture persistence. The architecture and related diagrams do not change with each new feature or bug fix. Rather, as long as the architecture is still usable -- code is either Views or Components -- the diagrams will be up-to-date and the team will know the guidance when adding to the application.
This article presented a software architecture rendered in UML. A UML Package Diagram showed the grouping of code and the relationships between the Packages. Class Diagrams displayed the static structure of the application. The Class Diagram introduced abstraction (notional AbstractView and AbstractComponent) facilitate discussion and relationships around similar SFCs.
The diagrams in this article were made with Sparx Systems Enterprise Architect.