From vision to architecture
The first essay gave a description of the vision of Backstage. It states how Backstage is aimed at building developer portals. This essay will build on top of the first one, and explore what the architecture of Backstage looks like. The analyses in this essay range from the main patterns used up till the containers and development views.
Main architectural pattern
The main architectural pattern used by Backstage is a common one for web applications. It relies on a Backend for Frontend architecture (BFF). The BFF architecture is used to create the client-facing Backstage web apps. The advantage of the BFF architecture is that it can move the system into a less-couples state than a monolith systems.
This code pattern allows team to iterate faster through the development of new features for Backstage, whilst maintaining control over the backend and not affecting the experience for users. The BFF architecture relies on microservices. Some examples of the microservices used within Backstage are the following:
- Core App API
- Backend Tasks
- Backend Common
- Plugin backends such as Lighthouse
Container view
The container view for Backstage is quite simple. Backstage has two main containers: Frontend Container and Backend Container. The Backend Container connects with a respective database.
The Frontend Container will include the components that build up the UI. Whilst the Backend Container contains the components that create the backend, such as the proxy component and the Service catalog backend.
The container view can be expanded however with the addition of new plugins. These plugins will be containerized in the own respective container. An example might be when a user wants to include the Lighthouse plugin. That will add a new Lighthouse Container to function as the Lighthouse backend. The plugin makes requests to the Lighthouse container, which runs a before mentioned microservice. An illustrated version of the container view can be seen in the diagram below.
Components View
In this section, a structural decomposition of Backstage into components with explicit interfaces and their inter-dependencies will be explained. The following diagram shows the components of Backstage as denoted using the C4 model.
The 3 main components in Backstage are:
- The App service.
- The Backend service.
- The Authentication service
The Authentication service makes an API available to the Backend service allowing the Backend service to lock certain parts of the API behind a log-in page. The Backend service makes an API available providing data to be processed and displayed to the user by the App service. The App service uses React to create a multi-page user interface available as a webpage.
Plugins can be used to extend the App service, Backend service and Authentication service.
Backstage combines multiple programs into one user interface. Each program integration (plugin) consists out of at least a front-end component integrated into the UI using React pages. Each plugin receives a route at which the front-end of that plugin can be viewed. Most of the plugins contain a backend component as well, which are integrated into the backend service as an Express app. Again receiving a route at which they can be accessed. These backend components can interact with either external services or local services through API calls. Local services can be a variety of different things ranging from databases to full applications. They are run using a container platform such as Docker, however a variety of other container platforms have been integrated into Backstage. A single plugin can contain multiple backend components.
The following diagram shows how Backstage might look when deployed inside a company which uses the Tech Radar plugin, the Lighthouse plugin, the CircleCI plugin and the software catalog.
The authentication provider used in the authentication service can be specified by the user, the default one used is the guest access provider which simply allows anyone to view anything.
Connectors View
The connectors view provides an overview of the tools and methods used to enable communication between components and containers. As the main type of connections used between components in Backstage are API calls using HTTP and returning JSON and database accesses which depend on specific plugin implementation a full connector view would be way too extensive (if it includes all plugins). These connectors are further discussed in the section API Design Principles.
Development view
For its development, Backstage needs a few prerequisites.
- Linux/MacOS/WSL for windows
- Node.js
- Yarn
- Docker
These prerequisites are the basis of Backstage. It can occur that depending on the type of plugin, users need additional dependencies installed. This will occur later in development and is redundant for the main Backstage repository. Docker is also not used everywhere. It is only used for software templates and Techdocs.
The repository is designed as a monorepo, meaning that different functionalities are within the same repository. The monorepo itself is split up into two components. These are 1) Packages and 2) Plugins.
The packages contain a lot of modules. To explain it in a suitable fashion, let us look at a condensed and highlighted version of this:
- app, Example component on what an App within Backstage could look like to enable local development
- backend, backend that app (frontend) can use
- catalog, All the code that deals with the Catalog
- cli, Custom made CLI that is used to call tools directly
- config, contains the way configuration data is read
- core, All the code that deals with the Core
- dev-utils, Helps setting up a plugin for isolated development
- e2e-tests, Contains end-to-end tests to test a full deployment
- storybook, Contains all the code for the storybook
For a more detailed picture, consult the overview:
The plugins component has a smaller separation. This is split between plugins that concern Frontend, Backend and GraphQL. This differentiation is made to have a separation of concerns and also to avoid conflict in the dependencies.
Finally, there is a possibility to add packages outside of the monorepo for the project. These however only contain functionality that is related to the project but for whatever reason, such as dependency issues, can’t be contained within there. Usually they are also more related to a meta level and don’t conflict with the core projects functionality. Currently, it is only the microsite that is set outside the repository. This package concerns the source code of backstage.io itself.
Run time view
When the user starts up Backstage, they only interact with the GUI component. This of course makes it very user friendly and does make for a good separation. The UI can bind multiple plugins to itself and through events inside the plugins, the plugin can handle requests of the user they made through the plugins. These requests can be processed by the backend side of the plugin. The requests can differ depending on the service it needs. If the plugin is within the purview of the organization, the request would be an API request to the backend, where the backend will process it and might make calls to a database as well, fetching information. However, if the plugin is from a third-party, a request outside of the system needs to be made. This is done through a provided proxy service, because the request will be blocked by CORS policies if this isn’t the case.
Realizing Key Quality Attributes
As discussed in the previous essay the most important key quality attributes are Ease of Integration and Learnability. Backstage has a big library of plugins contained in the main repo, as well as a large amount of community developed plugins to be used in a deployment of Backstage. Backstage allows plugins to integrated in every almost every part of the system, and plugins have a lot of freedom to decide how they integrate. Plugins can also vary in complexity. A very simple plugin may not require to be run on the server, only in the client browser. It is also possible for plugins to fully integrate with other plugins, or communicate with third party plugins. Furthermore every plugin can make use of the same, but separate logical database managed by Backstage. These features make Backstage very easy to integrate with, plugins can take many forms and many plugins have already been written.
The other big attribute Backstage focusses on is Learnability. Learnability means that new users of Backstage should be able to get up to speed quickly. New developers in a company should not need new credentials for the many backends and should not need to sift through code to know the architectural principles of the software. Backstage inherently makes learnability easier by centralizing all resources of a software service into a single point. Furthermore Backstage is in the process of developing a TechDocs documentation platform which allows for automated generation of documentation pages from version control systems. Integrating code documentation into the platform means that not only is the management of the service in one place, the documentation of the service is right next to it.
API Design Principles
There are two API’s used for intergrating plugins in Backstage. A plugin can have code running in the React client, and also code that can be run in the NodeJS backend.
React frontend API
In the frontend all plugins are tied together using the ApiRef instance as specified by React. This is a global object which has the single purpose of referencing other plugin interfaces. The idea behind this is that all plugins are able to communicate with each other through this object. A plugin can consume API’s from this object, but can also register new API’s to be used by other components. Registering an API is done using so called “API Factories”. In these API factories the plugins states what dependencies it has, and registers it on the global API object. This factory can then later on be called by a consumer to interact with it.
The consuming of an API is done using React hooks and is as simple as 1 the code below. Plugins can import a reference of the instantiated API object using the useApi
hook.
import React from 'react';
import { useApi, errorApiRef } from '@backstage/core-plugin-api';
export const MyComponent = () => {
const errorApi = useApi(errorApiRef);
// Signal to the app that something went wrong, and display the error to the user.
const handleError = error => {
errorApi.post(error);
};
// the rest of the component ...
};
NodeJS backend API
As discussed earlier Backstage uses Express as webserver framework. A backend plugin can register itself with Backstage with the createPlugin
method. Essentially what a backend plugin does is to add functionality to a route. For example a code line count plugin would register an API route under /lineCount
, and attach logic to count lines. Backstage will make this route available to the frontend. Furthermore Backstage will provide a database connection using the Knex library. Knex gives an object which can write to a database space, only available to the plugin. It is logically divided so that a plugin can not write outside its scope.
References
-
Backstage Utility APIs [Online]. Available: https://backstage.io/docs/api/utility-apis. [Accessed: 14-Mar-2022]. ↩︎