Robus Gauli
@robusgauli
Name
Work
More about me @ https://robus.dev
Architecture, Infrastructure, Operations a.k.a Drawing boxes and arrows
Robus Gauli
Tech
Go, Python, Rust, C/C++, K8s, Open X ....
8. Wait postgres version need to be above 10 for issues we can't figure out yet
......
Service broken down to multiple services
As function of concern (cart, billing, user, etc)
Single responsibility per Service
Thus simple in design
Separate modules/packages instead of separate service
Usually single database instead of database per service
In memory lock instead of Distributed lock
Thus simple in design
Independent Deployment of services
Faster Development to Deployment Cycle
Faster Build and Tests
Thus Agile
No service to service dependencies
No versioning and contract managements
No smoke tests or chaos testing
Thus Agile
Client
Server
Database
Monolith
server
Database
Client
server
Database
server
Database
server
Database
Microservice | SOA
server
Database
Client
server
Database
server
Database
server
Database
Client
Client
Microservice | Microfrontend | SOA
Configuration management
Cloud Resources Provisioning Tools
Infrastructure as a Code
Configuration management
Cloud Resources Provisioning Tools
Infrastructure as a Code
Configuration management
Cloud Resources Provisioning Tools
Infrastructure as a Code
MONOLITH
MICROSERVICE
Scalability
Scalability
Scalability
Simplicity
Agility
Reliability
Traceability
Testability
Debugability
Transport ( remote calls vs memory address hops JMP/RET)
Security
Operation
## Create a command: terraform init (via operational docker image proxy)
docker/terraform-init:
docker run \
--rm \
--entrypoint=terraform \
-v $(APP_ROOT):/app \
-w /app \
--env AWS_ACCESS_KEY_ID=$(AWS_ACCESS_KEY_ID) \
--env AWS_SECRET_ACCESS_KEY=$(AWS_SECRET_ACCESS_KEY) \
--env TF_CLI_CONFIG_FILE=$(TF_CLI_CONFIG_FILE) \
\
awsregistry/operational-deps-image init
2. Create build/test scripts that wraps your Operational Docker Image
Makefile
## Create a command
docker/build:
docker run \
--rm \
--entrypoint=yarn \
-v $(APP_ROOT):/app \
-w /app \
awsregistry/operational-deps-image build
Another example of build react app
Makefile
3. Execute the command
~/home
> make docker/terraform-init
From your terminal
docker/terraform-init:
docker run \
--rm \
--entrypoint=terraform \
-v $(APP_ROOT):/app \
-w /app \
--env AWS_ACCESS_KEY_ID=$(AWS_ACCESS_KEY_ID) \
--env AWS_SECRET_ACCESS_KEY=$(AWS_SECRET_ACCESS_KEY) \
--env TF_CLI_CONFIG_FILE=$(TF_CLI_CONFIG_FILE) \
\
awsregistry/operational-deps-image init
~/home
> make docker/terraform-init
Command to execute
Docker image with operational deps
upload
test
build
apply
Execution inside container
mounted source code inside container
~/home
> yarn build
~/home
> make docker/build
Before
Now
Yup that's it. No joke
Yup that's it. No joke
No dependencies to Build, Test, Lint, Upload, Deploy or anything that is required in your development lifecycle
make docker/build
make docker/build
make docker/test
make docker/test
make docker/deploy
make docker/deploy
To build
To test
To deploy
make docker/dry-run
make docker/dry-run
make docker/lint
make docker/lint
make docker/format
make docker/format
To dry run
To lint
To format
master:
- step:
name: Run Lint and Plan
deployment: prod-plan
script:
- make docker/terraform-lint
- make docker/terraform-plan-out
- step:
name: Run Test
deployment: prod-plan
script:
- make docker/test
- make docker/integration-test
- step:
name: Apply terraform plan
deployment: prod-apply
trigger: manual
script:
- make docker/terraform-apply
Sample Bitbucket CI scripts from project
Don't expect developer to manage dependencies.
Isolating Operational Complexity is equally, if not more important than isolating Application Complexity
Your CI/CD, Staging environments & Local should share common operational and application dependencies.
Language features
* Map/Reduce/Filter
* Classes & Objects
* First class functions, Immutability, closure, polymorphism, inheritance, ABC, etc
* Generators, async/await, coroutines, pattern matching, etc
* Modern error handling. I call it "throw everything and handle nothing"
Programming patterns*
* Singleton pattern
* Factory pattern
* Try/Except control flow
* Multi hierarchy pattern
Let's evaluate programming from the First principle/Axioms
What does it mean to program?
INPUT
f(x)
OUTPUT
3
sqr(x)
9
"robus"
len(x)
5
Query(user)
find(x)
{name: "john"}
robus.dev
http(x)
<html >
Side effects
None
None
IO
IO
Read from memory
int x = &y
Write to memory
x = 56
Conditional branching
if (x > 45) {...}
Control flow construct
for (..) {break}
Yup that's all. Verified by Turing and Alonzo Church
No try/catch/exception
No classes & objects
No generators & coroutines
And yet, it empowers 100% of cloud native infrastructure (docker, kubernetes, fargate, etcd, falco, lambdas, etc)
No inheritance
No map, reduce, filters, while, do while
No abstract base class and super/sub class
No macros and templates
No try/catch/exception
No classes & objects
No generators & coroutines
No inheritance
No map, reduce, filters, while, do while
No abstract base class and super/sub class
No macros and templates
Errors are just values.
Struct
X
for(...)
X
Interface
X
user := User.get(id)
mentors.add(user)
parents.add(user)
1
No error handling
try {
user := User.get(id)
} catch (USERMISSINGEXC) {
user := User.create({name: "mac"})
notifiy(USER_CREATED)
} finally {
mentors.add(user)
parents.add(user)
}
2
Using try/catch
err, user := User.get(id)
if (err == USER.MISSING) {
user = User.create({name: "mac"})
notify(USER_CREATED)
}
mentors.add(user)
parents.add(user)
3
Error as a value
One way of doing things
You might not require Singleton pattern if you don't have global state in your language
Rule of thumb: Less features => Good feature
Make sure errors are first class values. Ideally, no compilation without explicit error handling
Use language that has native construct for side effects management. For example: Interface to swap out I/O dependencies in Go/Java/Typescript, Monad to wrap side effects in Haskell and JS, etc
Compilation/Transpilation free
Dynamic Types
* Less code => Good code
* I call it "any type programming pattern"
Time to 200 OK
No compiler in your tooling means you are the compiler
Runtime crashes like these "234" + 34
Monkey Patching, Duck Typing
* Readability & Correctness
Dynamic types
* Performance
* Refactor
* Contracts via Types
Rely on strongly typed language as a guide during refactoring/changes.
Use language & tools that ensures program correctness using Static Types
* Value correctness [NEXT SLIDE]
* Memory and thread safety
* mypy, go race, rust analyzer, flow, typescript, elm, etc
Avoid "Monotype" language, or make it compilation target
* For example, JS is more of a compilation target than a language. [NEXT SLIDE]
* Dropbox had to introduce type annotation in a language to make sense of their 4 million lines of code.
Coding = 10% new code & 90% changes in average. Look at VIM's default mode
// Compile to JS from C
emcc -o main.js main.c
// Run via Node
node main.js
#include <stdio.h>
int sum(int* numbers, int size) {
int result = 0;
for (int i = 0; i < size; i++) {
int number = numbers[i];
result += number;
}
return result;
}
int main() {
int numbers[5] = {1, 2, 3, 4, 5};
int result = sum(numbers, 5);
printf("The result is: %d\n", result);
}
C to JS/WASM using emscripten compiler tool chain
Services with bounded concern
Scale
Distributed
Observability
Messaging
Gateways
AWS/Azure/GCP Services
Container orchestrations
Storage(s3/File Store, EBS/ABS)
Databases
SNS, SQS, Events Hub, Azure Service Bus
Durable & Steps
? Local development
? Debugability
? Metrics, Logs & Traces
? Compute
? Storage
? Functions
? Databases
? Observability
Note: Not a microservice architecture
? Cost
Tools & Solutions with open standards & protocols
Tools & solutions with ease of use in local settings
Tools & Solutions with managed support from cloud
Tools & Solutions with cloud agnostic property
Initial Architecture with Microservice trait ???