Software Guide Rails
The guide rails below are used by CRUK's Software Engineering teams to ensure alignment and aid decision making.
Summary
Guide Rail | Description |
---|---|
Language | TypeScript |
Version Control | GitHub - Product Monorepos |
Cloud Platform | AWS |
AWS Services | Serverless Lambda >> Fargate >> DynamoDB >> Aurora Serverless >> |
Environments | PR - ephemeral for testing Integration (int) - static for clients Production (prod) |
AWS Accounts | int - PR & Integration Environments prod - Production Environment |
Frontend Frameworks | Next.JS |
Frontend Form Libraries | React Hook Form | Zod |
Frontend Styling Libraries | Styled Components | CRUK React Component Library |
Frontend Testing Libraries | jest | vitest | React Testing Library | Cypress | Playwright |
Introduction
The Software Engineering team’s purpose is the design, development, operation of custom-developed software for the charity. This includes:
- External-facing websites such as the main Cancer Research UK website
- Web applications such as the Payments Web Service, Online Fundraising and Event Management
- Applications aimed at researchers, clinicians and internal users like the ECMC Trial Finder
- Data integration solutions like Person Hub or the U4BW interfaces
This document defines high-level principles that all software engineers at Cancer Research UK should adhere to. This promotes strategic alignment and consistency between teams whilst empowering engineers to make lower-level decisions as part of their day-to-day work. The main benefits of adhering to these principles are that:
- engineers are able to move between teams more easily thanks to familiar frameworks and patterns and
- technical decision making is quicker as higher-level decisions have already been made
Assurance approach
The assurance approach to ensure these guide rails are followed is to:
- Involve the software engineering team in early stages of initiatives where buy vs build decisions are made
- Ensure all code changes go through a code review, either by a peer or by a more senior Engineer; Lead Software Engineers are accountable for the guide rails being followed within their teams
Exception process
If an engineer needs to deviate from these guide rails, they are expected to produce a decision document supporting this, to be approved by the Senior Manager of Software Engineering or the Head of Engineering. This decision document should be kept with the rest of the product documentation (typically in the code repository) for future reference.
Engineering approach
When & how to engage with the Software Engineering team
The Software Engineering team’s purpose is to design, develop and operate custom-built applications for the charity where suitable off-the-shelf commercial alternatives do not exist on the market or where there is a strategic advantage to developing a custom solution.
Rationale
The Software Engineering team’s purpose is to design, develop and operate custom-built applications for the charity such as:
- Content Management System (CMS) websites such as the main Cancer Research UK website
- External-facing fundraising web applications such as the Payments Web Service, Online Fundraising and Event Management
- Web applications aimed at researchers, clinicians and internal users like the ECMC Trial Finder
- Data integration solutions like Person Hub or the U4BW interfaces that allow custom-built or off-the-shelf applications to communicate with each other
The Engineering strategy is not to develop custom applications where suitable off-the-shelf commercial alternatives exist on the market. Good examples are U4BW (Finance system), Workday (HR system), FirstClass (legacy management system).
Separate Engineering teams are in place for CRM/Marketing engineering and Business Intelligence.
The Software Engineering team should be engaged during the discovery phase of new products/services/initiatives to assess and estimate options, design custom solutions and mobilise the delivery team using either internal or external software engineers.
Further reading
Preferred language
The preferred language for all aspects of software engineering is TypeScript. This includes front-end, back-end, infrastructure-as-code, QA automation.
Rationale
Using a single language for all aspects of software engineering encourages collaboration between engineers and cross-skilling into areas that engineers are less familiar with. This makes the engineering team more flexible and efficient. Typescript is strongly typed and should indicate issues at compile time before they appear at run time
Further reading
Preferred cloud platform
The preferred cloud platform for software engineering is AWS. Specialist service providers/vendors will only be considered if they have a significantly superior offering to the equivalent AWS service. The preferred approach is to adopt its native services and libraries (as opposed to cloud-agnostic tools or abstraction frameworks).
Rationale
Focussing our skills on one cloud provider makes the most of the software engineers we have available. Using native services and libraries (e.g. AWS Lambda, AWS SDK, AWS CDK) rather than abstraction frameworks (e.g. Serverless Framework, Terraform) reduces complexity and allows us to recruit more easily.
Further reading
Preferred AWS services
We prefer using serverless services (e.g. prefer Lambda over Fargate, Fargate over EC2, Aurora Serverless over RDS).
Rationale
Serverless services require less coding and less maintenance to meet the same non-functional requirements
Further reading
Preferred approach to version control repositories (monorepo)
A monorepo is a software development strategy where the source code for multiple projects and packages are stored in a single git repository. At CRUK monorepos are applied at the product level. The monorepo approach means that the frontend, backend, infrastructure and shared packages are stored within the same repository for a given product.
Rationale
Engineers can change multiple components in a single PR. Refactors and adjustments to one package can be applied across all packages in a single commit/PR.
All product components and packages are stored in a single place making source code easier to find.
Works well alongside a microservice architecture where each component is its own package within a monorepo.
Testing of dependencies can be done in the same PR.
Keeping the monorepo at the product level allows git history against a single product and keeps the repo a manageable size. It also allows access restriction at the product level.
Further reading
- Best Practice
- Wikipedia Monorepo (https://en.wikipedia.org/wiki/Monorepo)
- Why monorepo (https://rushjs.io/pages/intro/why_mono/)
AWS accounts and test environments
Each product should have the following AWS accounts with the minimum following environments:
AWS Account: Integration (int)
- PR environments
- Integration environment (optional)
AWS Account: Production (prod)
Rationale
Fewer static environments (Reduces environment rot, lowers cost, removes bottlenecks) Quicker releases: having fewer environments from the PR to Production results in quicker releases as every change approved is merged and pushed to the Production environment. Aligned AWS accounts and environments across product teams. Clarifies what environments to connect across products.
Further reading
- Best Practice
- https://about.gitlab.com/blog/2020/01/27/kubecon-na-2019-are-you-about-to-break-prod/
- https://pipelinedriven.org/article/ephemeral-environment-why-what-how-and-where
- https://pipelinedriven.org/article/the-four-biggest-issues-with-having-static-environments
Front-End Engineering
Preferred React frameworks
Next.JS
Rationale
React has been chosen due to a combination of its ability to handle large scale complex frontends and it being mature, well supported and has a large community of users. React is backed by Facebook. The concepts such as one-way data flow means apps are usually predicable and easy to test and with its virtual DOM diff algorithms it can handle large complex changing front ends efficiently.
Next.JS is recommended for:
- Stand alone static pages with JS which are preferred over single page applications (npm run build && npm run export)
- Apps that require Server Side Rendering (SSR)
- Apps that need to be fast for the end user using Static Site Generation on the server (SSG)
- Apps that need to work with Social Sharing (as you need the head meta data to be there before it gets to the browser)
- Apps that where Search Engine Optimisation (SEO) is important
Further reading
Preferred JS/TS Form Libraries
React Hook Form (for forms), Zod (for validation)
Rationale
React Hook Form (RHF) is a flexible, mature, well-documented and actively maintained open source library, and it's an alternative to form libraries like Formik. What makes it stand out is its default performance story. The technique it leverages to achieve this performance is using uncontrolled forms (as opposed to controlled forms that you'd either implement with Formik or naively implement by hand in React) and this vastly reduces the number of re-renders. It uses a plug-in mechanism to integrate with validation libraries like Zod and Yup, and those specific integrations are supported by the official RHF project.
Further reading
- https://react-hook-form.com/
- https://zod.dev/
- https://blog.logrocket.com/controlled-vs-uncontrolled-components-in-react/
Preferred Styling Libraries
Styled Components, CRUK React Component Library
Rationale
Styled Components is a widely adopted, well supported and documented CSS in JS library. CSS in JS is a preferred method of styling as it is provides many benefits such as reusable components and automatic name spacing avoid the common issue with CSS selector collisions and issues with scalability. It also handles theming well, which is why the React component library has been built with it.
Further reading
Preferred Testing Libraries
Jest, Vitest, Cypress, React Testing Library, Playwright
Rationale
Jest is the most common test runner for modern JS development. It is used for rudimentary unit tests. Vitest is a modern contender that is Jest-compatible that should be a drop-in replacement for Jest. It includes a lot sane defaults and interesting concurrency modes for test execution.
Cypress is our preferred testing tool it is well known by our QA team. It can be used in interactive or headless mode where a real browser is used to test real browser behaviour. It has a rock solid and comprehensive API and multiple add-on modules written by its large community. The only caveat is if your tests need to jump multiple domains; in this scenario we would recommend using Playwright.
React Testing Library does a good job of being a fast framework that doesn’t require a browser. It uses real DOM elements to test with instead of shallow rendering like enzyme, so it is much more closely aligned with real user experience.
Further reading
- https://vitest.dev/guide/comparisons.html#jest
- https://testing-library.com/docs/react-testing-library/intro/
- https://www.cypress.io/
- (INTERNAL) Online Payments' frontend repo. Areas of interest:
vitest.config.ts
__tests__/setup.ts