This readme gives developers an overview over the system architecture and the development and operations workflows. It documents a full circle DevOps approach that encourages cross-functional collaboration and fast flow of change.
- The tech stack is centered around full stack JavaScript solutions.
- This Monorepo is based on npm workspaces which means it contains an npm root package and multiple child packages.
- Child packages are kept in different directories (infrastructure, apps, libs) based on their architectural role (inspired by Nx's approach).
- A child package can reference other child packages within the Monorepo as a code dependency to access its code.
- Lerna is used to run npm scripts across all packages. It determines the execution order of scripts (e.g. deployment scripts) based on the dependency topology.
- Configuration values are stored in environment variables and are not versioned. They are passed in via a local
.env
file or by specifying environment variables in the CI/CD runtime. - Staging and production infrastructure environments are kept similar.
graph LR
User["User"]
subgraph "Vercel"
NextApp("Next App")
end
User -.-> NextApp
Infrastructure contains the setup for cloud-based environments and services and is required to deploy and operate applications. The setup is described through documentation or through infrastructure as code solutions.
- Vercel: Cloud platform for deploying and operating web apps.
Applications are executables that are deployed to and operated on infrastructure. They use internal and external libraries as code dependencies. Applications are configured through environment variables that are passed in at runtime or compiled into the application at compile time.
- Next App: Next.js web app with integrated REST API.
Libraries are used as code dependencies by applications or other libraries. They typically implement specific functionality (e.g. utility functions, user interface components) or the network interaction between applications on the client side as a "client library" or the server side as an "api library". Libraries can be published to make them available for external applications outside the Monorepo. Libraries can define their own build process or just provide source files that are compiled by the build process of the consuming application. Libraries should not read environment variables but receive their configuration from the application through some kind of initialization.
- React API Client: Client library that provides React Hooks to access the REST API.
- Express API: Server library that implements a REST API as an Express middleware.
- Trunk Based Development is used.
- The
main
branch is the trunk and contains the latest version of the software that should be deployable to production at all times. - Changes to
main
automatically trigger a deployment to the staging environment. - Production deployments are triggered by merging the
main
branch into theproduction
branch. - Changes are developed in short-living feature branches that are based on and merged back into
main
. - Both
main
andproduction
should only contain commits with deployable versions of the software so that each commit actually represents a past deployment and rollbacks can be done by deploying any previous commit. Therefore, commits should always be squashed when merged to keep intermediate states out of the history. production
should never be changed manually and never backmerged intomain
. Emergency hotfixing can be done by cherry-picking changes frommain
intoproduction
.- Feature branches should be short-living to accelerate lead time and to receive early feedback. It is better to integrate even unfinished changes in a disabled state (e.g. commented out) than to delay integration and risk complex merge conflicts.
How to set up a local development environment:
- Install Node.js
- Install dependencies:
npm install
- Copy
.env.example
to.env
and set up local environment variables:cp .env.example .env
How to develop and debug changes locally:
- Start local development servers:
npm run dev
- Open http://localhost:3000 in browser
How to create a local production build:
- To create a production build, run:
npm run build
- To start the production build locally using
.env
environment variables:npm start
How to run automated tests locally:
- Run tests:
npm run test
- Run linting:
npm run lint
How to develop changes and integrate them:
- Create a feature branch
feature/...
based onmain
- Commit changes
- Rebase onto
main
to receive latest changes - Create a pull/merge request
feature/... -> main
- Ask for a review and await approval
- Squash, merge and close the request
- Check
staging
environment deployment pipeline
How to create a production release:
- Create a pull/merge request
main -> production
- For emergency hotfixes: Create the pull/merge request by cherry-picking just the specific commit with the emergency changes from
main
- For emergency hotfixes: Create the pull/merge request by cherry-picking just the specific commit with the emergency changes from
- Ask for a review and await approval
- Squash, merge and close the request
- Check
production
environment deployment pipeline
Things to do on a regular basis:
- Upgrade vulnerable package dependencies to receive latest security updates:
npm audit npm audit fix
- Establish observability by collecting and processing telemetry data.
- Define alerting conditions that require human intervention.
- Schedule on-call shifts for responders and set up a notification system to alert them.
- Document diagnosis and mitigation procedures in runbooks.
- Learn from failures by doing postmortems.
- Vercel: Collects application logs.
- Assess and document the impact and severity of the incident.
- Use runbooks/ to diagnose and resolve the incident.
- Identify follow-up improvements (e.g. development tasks, runbook improvements).