Greenfield microservices with Go & Java 21: from DDD to production
Starting from a blank canvas is both the most exciting and most dangerous position in software engineering. With no legacy to constrain you, every architectural decision you make in the first few weeks will shape the platform for years. This post covers how we approach greenfield platform incubation at CloudPrimacy — the decisions we make early, why we make them, and how we get from whiteboard to production-grade delivery.
Start with the domain, not the technology
The single most common mistake on greenfield projects is reaching for a tech stack before the domain is understood. We spend the first engagements doing event storming and domain modelling — identifying bounded contexts, aggregates, and the language the business actually uses. The technology follows the domain shape, not the other way around.
On a recent fintech platform, the domain model surfaced four natural bounded contexts: identity, accounts, transactions, and notifications. Each became its own service, owned by a sub-team, with a well-defined contract at the boundary. That clarity upstream made every downstream technical decision easier.
Why Go for some services, Java 21 for others
We don't have a religion about language choice — we match the language to the workload characteristics:
- Go — for high-throughput, latency-sensitive services where startup time and memory footprint matter. API gateways, event processors, and lightweight microservices. Go's concurrency model (goroutines, channels) maps naturally to event-driven workloads, and its single-binary deployment simplifies containerisation significantly.
- Java 21 — for services with complex business logic, rich domain models, or where the team's existing expertise is Java. Virtual threads (Project Loom) in Java 21 make previously blocking I/O patterns significantly cheaper, and the ecosystem for domain modelling (records, sealed classes, pattern matching) is mature. We use Micronaut rather than Spring Boot where cold start time is a concern.
The key is making the choice deliberately and documenting the reasoning — so future engineers understand the intent rather than guessing.
Architecture principles we apply consistently
Regardless of language, we apply a consistent set of principles across every service:
- Ports and adapters (hexagonal architecture) — business logic has zero dependency on infrastructure. This makes testing fast and migration cheap.
- Event-driven by default — services communicate via events (SQS, EventBridge) rather than synchronous HTTP where possible. This keeps services truly decoupled and makes the system resilient to downstream failures.
- Schema-first APIs — OpenAPI specs are written before implementation, not generated from it. This enforces contract discipline and enables parallel frontend/backend development.
- Observability from day one — structured logging (JSON), distributed tracing (AWS X-Ray), and CloudWatch dashboards are part of the definition of done, not afterthoughts.
Infrastructure: Terraform from the start
We provision everything with Terraform. No console-click infrastructure, ever. This isn't dogma — it's practical: it makes environments reproducible, drift detectable, and onboarding new engineers fast. We structure Terraform into modules per bounded context, with a shared networking and IAM foundation layer. State is remote (S3 + DynamoDB locking) from day one.
On AWS, our standard greenfield stack is: ECS Fargate for services that need persistent compute, Lambda for event-driven workloads, API Gateway for external-facing endpoints, RDS Aurora Serverless or DynamoDB depending on the data shape, and CloudFront + S3 for static frontends.
CI/CD: ship from day one
We set up CI/CD pipelines before writing the first line of application code. GitHub Actions with environment-specific workflows: PR builds run tests and linting, merges to main deploy to staging automatically, and production deployments require a manual approval gate. Docker images are built, tagged with the Git SHA, and pushed to ECR. No "we'll sort the pipeline out later" — later never comes.
Blue/green deployments via ECS allow zero-downtime releases. Feature flags (we use a lightweight SSM Parameter Store pattern rather than a third-party service for most cases) allow safe incremental rollout of new functionality.
Frontend: Next.js on the edge
For customer-facing frontends we use Next.js — server-side rendering for SEO-critical pages, static generation for content, and React for interactivity. Deployed to CloudFront + S3, with ISR (Incremental Static Regeneration) where content changes frequently. TypeScript throughout, Tailwind for styling, and a strict component library contract between design and engineering.
What we've learned
A few things that still surprise teams coming to greenfield work for the first time:
- The first month sets the culture — code review standards, naming conventions, commit message discipline, and on-call processes established early become self-reinforcing. Don't defer them.
- Over-communicate architecture decisions — Architecture Decision Records (ADRs) in the repo, not just in someone's head. Future you will thank present you.
- Resist premature optimisation — a well-structured monolith beats a poorly designed microservices mesh. Start with clear bounded contexts and extract services when the boundary is proven, not assumed.
- Distributed teams need explicit async culture — across time zones, decisions made in Slack DMs become blockers. Write things down. Default to documentation over conversation.
Greenfield is a privilege. The blank canvas only stays blank for a few weeks — make those weeks count. If you're embarking on a new platform and want an experienced engineering lead in the room from day one, get in touch.