The battle against complexity in web development is a constant tug of war. We give a little to get something new, we take it back to make it simpler. Progress is good. Complexity is a bridge. Simplicity is the destination.
I love this quote from DHH. For those unfamiliar, DHH is the founder of Ruby's Rails framework, and is a huge proponent of the "mighty monolith." While I admire DHH's stance on simplicity, we diverge on architectural ideals. Modern tooling streamlines microservices management:
- Container Orchestration in the cloud automates deployment and scaling of 12-factor apps.
- Service meshes externalize common code for inter-service communication. This enhances traffic management and security.
- Schemas predefine APIs, often automating client and server code generation.
- Federated GraphQL consolidates GraphQL schemas, optimizing API organization and microservice communication.
- Events foster service independence, boosting scalability and resiliency via asynchronous communication.
Tangentially, an essential part of our job is knowing when to buy versus build. When services are small and have a singular purpose, there's often a SaaS solution. There's no sense in reinventing the wheel, after all.
Containerization changed everything. Long ago are the days of needing a version manager for every language you’re working with. asdf solves part of the problem, but docker renders most version tooling obsolete. Docker compose makes stitching apps, data stores, and tooling together painless. In other words, setting up our local environment is as easy as setting up docker...
Configure docker compose files with tooling to run any language or framework commands. I prefer them to Makefiles.
# pro-tip
alias dr='docker compose run --rm '
dr npm install left-pad
dr npm update
dr go get -d golang.org/x/net
dr go mod tidy
dr bundle install
dr bundle exec rails db:setup
Spinning up a local environment simply consists of:
docker compose up -d
Let’s keep code styling consistent with linters. There is community tooling for all languages, so be sure to configure your editor.
Let's protect ourselves from... ourselves. CI enables us to do things like:
- verify our tests are passing
- check our code against linters
- static code analysis
- build various artifacts
- keep our dependencies current
...all without having to know much about any given repo. Configure integration failures to be verbose, with clear steps to rectify any errors.
GitHub Actions is a wonderful solution. Use reusable workflows and templates to add Actions to a repo.
Embracing GitOps, tools like Flux and ArgoCD enable pull-based deployments straight to Kubernetes. This keeps git as our single source of truth! Operators within the Kubernetes cluster align their state with the source code, minimizing the system's attack surface and guaranteeing the capture of all changes.
GitOps also centralizes infrastructure declarations. While Terraform is a go-to, cloud providers offer their alternatives.
Security is like an onion...
- Starting with code, employ static analysis tools to catch vulnerabilities before they reach production. CI should be running a suite of security checks with every commit.
- Prefer hardened, minimal base images and employ network policies to create a fortress around services. Terraform isn’t just for provisioning; it’s for crafting a secure foundation.
- At runtime, utilize a defense-in-depth strategy. Monitoring, logging, and alerting systems keep us informed, while an incident response plan ensures we're ready to act when necessary.
- Ensure we're always a step ahead. Embrace continuous education and adherence to best practices like regular patching and updates.
So... your PR was merged. Congratulations! An image was built, tagged, and pushed up to the container registry. An operator within Kubernetes has presumably detected the new tag, pulled the image, and started a canary rollout. Incoming requests are gradually diverted to new pods. Your changes are taking traffic. Now what?
If you can afford Datadog, go with them as they're best-in-class. Grafana's LGTM stack is excellent for self-managed setups. Regardless of the tool, microservices necessitate advanced observability, and distributed tracing is critical. Logs and metrics still have their utility, but some companies have transitioned to event-based systems.
Last, but not least, always remember to go SLO to go fast!
This README has been optimized for accessibility based on GitHub's blogpost "Tips for Making your GitHub Profile Page Accessible".