PROFESSIONAL WORK · 2026
Java Services, AWS Platform, and Reliability
My current work on a subledger platform, in one case study: Java service development, AWS platform and event-driven systems, reliability and multi-region infrastructure, observability and SLOs, and zero-downtime infrastructure migrations.
This case study is a sanitized explanation of my contribution. Internal names, architecture details, and business information have been omitted or generalized.
Context
Subledger Technology platform inside Asset & Wealth Management. The platform spans on-prem and multi-region AWS; my work sits on the service layer, its infrastructure, and its operations.
01
Java Application and Service Engineering
Contributing to Java service development on a subledger platform - application changes, configuration and dependency updates, integration work, and merge-request review in GitLab.
- Problem
- Service changes had to land safely in a platform with many integration points.
- What I contributed
- Contributed to: Developing Java application and configuration changes, integrating services with platform components, and taking changes through GitLab merge-request review, build, and deployment.
- Result
- Changes ship through reviewed merge requests with build and integration validation.
Key decision
Keep integration behaviour covered by tests that run in the pipeline rather than relying on manual verification after deployment.
Why
Integration mistakes - a changed contract, a missing configuration value, an unexpected dependency version - were the failures most likely to surface late and cost the most time to trace.
Trade-off
Pipelines take longer and the tests need maintenance when contracts change, but failures are attributed before promotion instead of after.
Show technical details ↓Hide technical details ↑
Scope
This case study covers my contribution to defined services rather than ownership of the wider platform.
Engineering problem
Application changes in a platform with many integration points can fail late - at build, dependency resolution, or integration time - rather than in the change itself.
Constraints
- Every change goes through merge-request review before it can be deployed
- Services must stay compatible with existing consumers and upstream data contracts
- Dependency and build changes must not alter runtime behaviour silently
Technical approach
- Developing Java application and configuration changes within a defined service scope
- Updating build and dependency configuration when versions or transitive conflicts require it
- Integrating services with platform components such as messaging, storage, and relational data
- Raising, reviewing, and revising changes through GitLab merge requests
- Adding or extending tests so integration behaviour is checked before deployment
- Troubleshooting behaviour reported in higher environments and production
Alternatives considered
- Relying on manual verification in a shared environment after deployment (faster per change, but failures surface later and are harder to attribute)
- Pinning every dependency indefinitely to avoid build churn (stable short-term, but accumulates upgrade risk)
Failure cases and edge cases
- Transitive dependency upgrades that compile cleanly but change runtime behaviour
- Configuration values present in one environment and absent in another
- Changes that pass in isolation but break a consumer's expectations
Challenges
- Keeping integration contracts stable while services evolve
- Diagnosing failures that originate in configuration or dependency changes rather than application logic
Outcome
Application, configuration, and integration changes in my scope ship through reviewed merge requests with build and integration checks, so integration problems are caught before deployment rather than in a shared environment.
Technologies
- Java
- REST APIs
- PostgreSQL
- GitLab
- GitLab CI
Ownership breakdown
Wider system context
- The wider platform, its architecture, and its release process are owned by the broader team
My contribution
- Service integration design within my scope
- Merge-request review of related changes
Components I personally implemented
- Java application and configuration changes within my service scope
- Build and dependency updates for the services I worked on
- Tests covering integration behaviour for those services
Components I investigated
- Build, dependency, and integration failures on the services I worked on
Components I validated
- Service behaviour through pipeline builds and integration tests
What I learned
Most of the time I spent on failing changes traced back to configuration or dependency differences rather than application logic, which is why I now check those first.
What I would improve
I would push more contract-level checks into the pipeline so a change that breaks a consumer fails in review rather than in a shared environment.
02
AWS Platform and Event-Driven Systems
Building and maintaining AWS-native services with CDK and CloudFormation - ECS Fargate, Lambda, API Gateway, Aurora PostgreSQL - and event-driven integrations over Kafka/MSK, Debezium CDC, EventBridge, and SQS.
- Problem
- New services needed a repeatable AWS deployment and event-integration pattern.
- What I contributed
- Contributed to: Building and maintaining service infrastructure with AWS CDK and CloudFormation, and developing event-driven integrations using Kafka/MSK, Debezium CDC, EventBridge, SQS with dead-letter queues, and S3.
- Result
- Deployment and event wiring moved into version-controlled infrastructure definitions.
Key decision
Give every asynchronous consumer an explicit dead-letter path instead of relying on retries alone.
Why
A single unprocessable message can otherwise stall a consumer or be silently dropped; a dead-letter queue keeps the failure visible and the stream moving.
Trade-off
Dead-letter queues need monitoring and a replay path, which is extra operational surface per consumer.
Show technical details ↓Hide technical details ↑
Scope
This case study covers my contribution to a defined service and infrastructure scope rather than ownership of the wider platform.
Engineering problem
New services needed a consistent way to ship on AWS - private networking, container runtime, database access, auth - and a consistent way to move data between services asynchronously without each team inventing its own pattern.
Constraints
- Everything provisioned through code review, not the AWS console
- Services had to fit a hybrid model spanning on-prem and multi-region AWS
- Traffic had to stay on private networking paths
- Event delivery needed explicit failure handling rather than best-effort retries
Technical approach
- Defining service infrastructure in CDK and CloudFormation so the deployment shape is reviewable alongside the code
- Running services on ECS Fargate, with Lambda for event-driven and asynchronous edges
- Using Aurora PostgreSQL as the system of record, with schema changes applied through Flyway migrations
- Exposing services through API Gateway with routing and authentication expressed as infrastructure code
- Keeping traffic on private networking paths using VPC endpoints, PrivateLink, and hybrid connectivity
- Developing event-driven integrations over Kafka/MSK and Debezium change data capture, with EventBridge routing, SQS queues, dead-letter queues for poison messages, and S3 for durable payloads
Alternatives considered
- Console-driven networking, routing, and auth configuration (faster initially, but invisible to source control and prone to drift)
- Synchronous service-to-service calls instead of events (simpler to trace, but couples availability of the two services)
- Application-level polling instead of change data capture (fewer moving parts, but higher latency and more load on the database)
Failure cases and edge cases
- Duplicate event delivery requiring idempotent consumers
- Change-data-capture connector restarts replaying from an earlier offset
- Messages landing in a dead-letter queue with no owner watching it
- Cold-start behaviour on Lambda paths at the asynchronous edges
Challenges
- Keeping the AWS-native slice consistent with on-prem services in the hybrid model
- Making asynchronous integrations safe to retry without duplicating effects
- Encoding routing and auth as infrastructure code without slowing routine changes
Outcome
Services in my scope ship with a consistent, reviewable deployment shape on AWS, and their event integrations have explicit retry and dead-letter behaviour instead of implicit best-effort delivery.
Technologies
- AWS
- AWS CDK
- CloudFormation
- ECS Fargate
- AWS Lambda
- API Gateway
- Aurora PostgreSQL
- Flyway
- Kafka / MSK
- Debezium
- EventBridge
- SQS
- S3
Ownership breakdown
Wider system context
- The wider platform spanning on-prem and multi-region AWS is owned by the broader team
My contribution
- Conventions for routing and authentication expressed as infrastructure code
Components I personally implemented
- Service infrastructure-as-code in CDK within my scope
- Containerised services on ECS Fargate backed by Aurora PostgreSQL within my scope
- Lambda handlers and queue consumers for asynchronous edges within my scope
Components I integrated
- Kafka/MSK topics, Debezium change data capture, EventBridge rules, SQS queues and dead-letter queues, and S3, from the service side
- The hybrid API gateway layer spanning on-prem and multi-region AWS, from the service side
Components I investigated
- Event replay, duplicate delivery, and Lambda cold-start behaviour within my scope
Components I validated
- Service and event-flow behaviour through integration tests gated in CI
What I learned
Infrastructure that is reviewable in the same diff as the code is easier to reason about than console configuration, especially when the same change has to ship across regions.
What I would improve
I would invest more in a shared construct library so a new service can adopt the platform defaults for networking, routing, and event wiring in a few lines.
03
Reliability and Multi-Region Infrastructure
Strengthening deployment and recovery behaviour with blue-green releases, ECS Auto Scaling, multi-region disaster recovery on Aurora Global Database, and fault-injection exercises using AWS FIS.
- Problem
- Recovery behaviour was assumed rather than exercised.
- What I contributed
- Contributed to: Contributing to blue-green deployment and auto-scaling configuration, and executing multi-region disaster-recovery and fault-injection exercises to check recovery behaviour.
- Result
- Failover and rollback paths are rehearsed and their behaviour recorded.
Key decision
Exercise failure paths deliberately with fault injection instead of waiting for a real incident to reveal them.
Why
Recovery configuration that is never exercised tends to be correct only on paper; scaling and failover assumptions are easiest to check while nobody is paged.
Trade-off
Experiments cost time and need careful scoping so they do not affect real traffic or data.
Show technical details ↓Hide technical details ↑
Scope
This case study covers my contribution to reliability work on defined services rather than ownership of the platform's resilience strategy.
Engineering problem
Deployments and regional failure paths existed, but their behaviour under load and during failover was largely assumed rather than observed.
Constraints
- Exercises had to run without risking production data
- Rollback had to be possible at any point during a release
- Cross-region replication lag had to be accounted for in recovery expectations
Technical approach
- Contributing to blue-green deployment configuration so a release can be shifted back without redeploying
- Tuning ECS Auto Scaling policies against observed load rather than fixed capacity guesses
- Configuring and exercising multi-region disaster recovery on Aurora Global Database
- Executing fault-injection experiments with AWS FIS to observe how services behave when a dependency degrades
- Recording what each exercise showed, including the cases where recovery was slower than expected
Alternatives considered
- Relying on documented runbooks alone (cheap, but does not verify the system behaves as the runbook assumes)
- In-place rolling deployments only (simpler, but rollback is slower than shifting traffic back)
Failure cases and edge cases
- Failover completing while replication lag left recent writes behind
- Auto Scaling reacting after the load spike had already caused errors
- Health checks reporting healthy while a downstream dependency was degraded
Challenges
- Designing experiments that are informative without being risky
- Setting recovery expectations that account for replication lag
Outcome
Failover and rollback paths for the services in my scope are rehearsed rather than assumed, and the observed behaviour - including slower-than-expected cases - is written down.
Technologies
- AWS
- ECS Fargate
- ECS Auto Scaling
- Aurora Global Database
- AWS FIS
- CloudFormation
Ownership breakdown
Wider system context
- The platform's overall resilience strategy is owned by the wider team
My contribution
- Blue-green deployment configuration for services in my scope
- ECS Auto Scaling policies for those services
Components I integrated
- Aurora Global Database replication into the recovery path for my scope
Components I investigated
- Failover, scaling, and degradation behaviour observed during exercises
Components I validated
- Recovery behaviour through disaster-recovery and AWS FIS exercises
What I learned
A recovery path that has never been exercised is an assumption. Running the experiment is usually cheaper than discovering the gap during an incident.
What I would improve
I would run fault-injection experiments on a regular schedule rather than around specific changes, so drift in recovery behaviour is noticed early.
04
Observability and SLOs
Building service dashboards and SLO-based monitoring with Prometheus, Grafana, and CloudWatch, and using them during production troubleshooting.
- Problem
- Alerts fired on resource metrics that did not always mean user-visible impact.
- What I contributed
- Contributed to: Developing and integrating service metrics, dashboards, and SLO-based alerts across Prometheus, Grafana, and CloudWatch, and using them to troubleshoot production behaviour.
- Result
- Monitoring reflects service-level objectives and supports faster troubleshooting.
Key decision
Alert on service-level objective burn and dead-letter growth rather than on raw CPU or memory thresholds.
Why
Resource thresholds produced pages nobody could act on, while real consumer-visible failures could stay invisible; objective-based alerts describe impact.
Trade-off
Objectives need agreement and periodic revision, and a burn-rate alert is less immediately obvious than 'CPU is high'.
Show technical details ↓Hide technical details ↑
Scope
This case study covers monitoring work on defined services rather than ownership of the platform's observability stack.
Engineering problem
Monitoring was resource-centric: alerts fired on CPU, memory, or queue depth, which did not reliably indicate whether the service was actually failing its consumers.
Constraints
- Signals had to work across both AWS-native and on-prem service paths
- Alerts had to be actionable enough to page on
- Dashboards had to be usable by someone unfamiliar with the service internals
Technical approach
- Instrumenting services with request, latency, error, and queue-processing metrics
- Defining service-level objectives for availability and latency on the paths consumers depend on
- Building Grafana dashboards that show the objective first and the supporting resource metrics second
- Wiring CloudWatch metrics, logs, and alarms for AWS-managed components such as queues, databases, and Lambda paths
- Alerting on objective burn and on dead-letter growth rather than on raw resource thresholds
- Using these signals during production troubleshooting to narrow a symptom to a layer
Alternatives considered
- Keeping threshold alerts on resource metrics (simple to configure, but weak signal-to-noise)
- Relying on log searches during incidents only (flexible, but slow and dependent on knowing what to search for)
Failure cases and edge cases
- Metrics missing for a short window after a deployment, briefly resembling an outage
- Objectives satisfied in aggregate while one consumer path was failing
- Alerts that fired correctly but pointed at a symptom one layer below the cause
Challenges
- Choosing objectives that reflect consumer expectations rather than convenient numbers
- Keeping dashboards readable as the number of services grew
Outcome
Services in my scope have dashboards and alerts tied to service-level objectives, and production investigations start from a signal that indicates consumer impact rather than a resource metric.
Technologies
- Prometheus
- Grafana
- CloudWatch
- SLOs
- AWS
Ownership breakdown
Wider system context
- The platform-wide observability stack is operated by the wider team
My contribution
- Definition of availability and latency objectives for my scope
Components I personally implemented
- Service instrumentation and dashboards for services in my scope
- SLO-based alert rules for those services
Components I integrated
- CloudWatch metrics, logs, and alarms for AWS-managed components
Components I investigated
- Production issues using metrics, logs, and dashboards
Components I validated
- Alert behaviour against real incidents and exercises
What I learned
An alert is only useful if someone can act on it. Tying alerts to objectives made it clearer which pages needed a response and which needed a fix in the monitoring itself.
What I would improve
I would connect dashboards more directly to trace data so moving from an objective breach to the failing request path takes fewer steps.
05
Infrastructure as Code and Zero-Downtime Migrations
Executing CloudFormation and database migrations on running services without downtime, using staged changes, Flyway migrations, and reviewable infrastructure definitions.
- Problem
- Stack and schema changes risked downtime or resource replacement on live services.
- What I contributed
- Implemented and contributed to: Executed CloudFormation stack and schema migrations on live services in stages, and onboarded services onto the reviewed infrastructure-as-code workflow.
- Result
- Migrations completed without downtime, with a rollback position at each stage.
Key decision
Make every schema change backwards-compatible for one release before removing anything.
Why
During a rollout both the old and new application versions run at once, so a destructive change in the same step breaks whichever version is not yet updated.
Trade-off
A single logical change becomes several releases, which is slower but leaves a safe rollback position at each stage.
Show technical details ↓Hide technical details ↑
Scope
This case study covers migrations I executed on defined services rather than a platform-wide migration programme.
Engineering problem
Some infrastructure changes replace resources rather than update them, and some schema changes break running application versions - both can cause downtime if applied in one step.
Constraints
- Services had to stay available while the change was applied
- Every change had to be reviewable in source control before it ran
- Old and new application versions had to work against the same schema during a rollout
Technical approach
- Reading change sets before applying them, to see which resources would be replaced rather than updated
- Splitting risky changes into stages that are each safe on their own
- Making schema changes backwards-compatible first with Flyway migrations - add, backfill, switch reads, then remove
- Pairing infrastructure changes with blue-green traffic shifts where a resource had to be replaced
- Onboarding services onto the reviewed infrastructure-as-code workflow so future changes follow the same path
- Verifying behaviour with metrics and dashboards during and after each stage
Alternatives considered
- Applying the full change in one deployment window (fewer steps, but no safe rollback and risk of downtime)
- Taking a short maintenance window (predictable, but unnecessary once changes are staged properly)
Failure cases and edge cases
- CloudFormation updates that quietly replace a resource and change its endpoint
- Migrations that lock a table long enough to time out requests
- Backfills that must run in batches to avoid replication lag
Challenges
- Predicting which infrastructure changes cause replacement
- Sequencing schema changes so both application versions keep working
Outcome
Stack and schema migrations in my scope were applied to running services without downtime, and each stage left a position the change could be rolled back from.
Technologies
- CloudFormation
- AWS CDK
- Aurora PostgreSQL
- Flyway
- GitLab CI
Ownership breakdown
Wider system context
- The wider platform's infrastructure standards are owned by the broader team
My contribution
- The reviewed infrastructure-as-code workflow used by those services
Components I personally implemented
- Staged CloudFormation and schema migrations on services in my scope
- Flyway migration sequences for backwards-compatible schema changes
Components I investigated
- Change-set behaviour and migration locking before applying changes
Components I validated
- Service availability during and after each migration stage
What I learned
Reading the change set before applying it is the cheapest step in the whole migration, and it is the one that most often changes the plan.
What I would improve
I would automate a pre-apply report that flags replacement-causing changes and long-locking migrations before a reviewer approves them.