Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
java: ["17"]

steps:
- uses: actions/checkout@v4
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: ${{ matrix.java }}
cache: maven

- name: Build and run tests
run: mvn -B -DskipTests=false clean verify

- name: Build app Docker image
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
run: |
docker build -t java-microservices-app:latest ./app

30 changes: 30 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Maven
**/target/
**/release/

# Eclipse / IntelliJ
.classpath
.project
.settings/
*.iml
.idea/

# OS files
.DS_Store
Thumbs.db

# Logs
*.log

# Maven wrapper
.mvn/wrapper/maven-wrapper.jar

# local maven repo
.m2/

# Docker
docker-compose.override.yml

# VS Code
.vscode/

119 changes: 119 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,120 @@
# Java Microservices

This repository is a blueprint and starter kit for building high-performance, production-ready Java backend systems that can be implemented either as a performant monolith or as a scalable set of microservices.

## Goals
- Provide a pragmatic, senior-backend-developer-approved architecture and tooling selection.
- Show recommended technology choices (Spring Boot + Quarkus) and cross-cutting tools for DB, messaging, caching, CI/CD, observability and security.
- Offer an incremental path: start with a modular high-performance monolith and split to microservices where it makes sense.

## High-level vision
- Keep core business logic domain-driven and framework-agnostic.
- Design for observability, security, and automation from day one.
- Use modern, proven tools to enable fast developer feedback and safe production deployments.

## Key non-functional requirements
- Performance: low latency (sub-100ms P95 for typical API calls), high throughput.
- Scalability: horizontal scalability for stateless services, and appropriate patterns for stateful services.
- Reliability: graceful degradation, retries, bulkheads, and circuit breakers.
- Maintainability: small modules/contexts, strong typing, and automated tests.
- Observability: structured logs, distributed tracing, metrics, and alerting.

## Recommended tech stack
- JVM & language: Java 17 or 21 (LTS), use modern language features where helpful.
- Frameworks:
- Spring Boot 3.x for full-featured, ecosystem-rich services (REST, Spring Data, Spring Security).
- Quarkus for performance-sensitive services (fast startup, low memory) and native compilation where needed.
- Build tools: Maven or Gradle (pick one consistently). Provide sample Maven setup by default.
- Database: PostgreSQL (primary OLTP store). Use R2DBC for reactive services where appropriate; otherwise use JDBC via Spring Data JPA.
- Migrations: Flyway (or Liquibase) for database schema/version control.
- Caching: Redis (for distributed caches) or Hazelcast (in-memory grid) when needed.
- Messaging / events: Apache Kafka (event-driven, durable streaming) and RabbitMQ (if you need simpler broker semantics).
- API protocols: REST (OpenAPI), gRPC for high-performance polyglot RPC, GraphQL optional for rich client queries.
- Security: OAuth 2.0 / OIDC (Keycloak or Auth0), JWT tokens, Spring Security, and Vault for secrets.
- Observability: OpenTelemetry (traces + metrics), Jaeger/Tempo for tracing, Prometheus for metrics, Grafana for dashboards.
- Testing: JUnit 5, Mockito, Testcontainers (for integration tests using real Postgres/Kafka), Pact for contract testing.
- Performance testing: Gatling or k6 for load tests; JMH for microbenchmarks.
- Containerization / orchestration: Docker, Kubernetes (K8s), Helm charts; optionally use Kustomize or ArgoCD for GitOps.
- CI/CD: GitHub Actions or GitLab CI for pipelines, with stages for build, test, security scans, container publish, and deploy.
- Secrets & Config: HashiCorp Vault for secrets, Spring Cloud Config / Consul or environment-driven 12-factor config for app configuration.

## Architecture patterns & design principles
- Start as a modular monolith (multi-module Gradle/Maven project) organized by bounded contexts. This gives fast developer feedback and avoids premature distributed systems complexity.
- Apply Hexagonal / Ports & Adapters architecture to keep business logic independent from frameworks and infrastructure.
- Use Domain-Driven Design (DDD) to identify bounded contexts and where to split into microservices.
- Prefer asynchronous messaging and event-driven integration for inter-service communication when eventual consistency is acceptable.
- Use API Gateway for external APIs (rate-limiting, authentication, routing). Keep internal APIs lightweight.
- Use health checks (liveness/readiness), graceful shutdown, and resource limits for containers.

## Project layout suggestions
- Monolith (modular):
- /app (service application starters) - Spring Boot / Quarkus launchers
- /domain (shared domain model & services)
- /infrastructure (db, messaging, cache adapters)
- /api (REST controllers / gRPC endpoints)
- /integration-tests (Testcontainers-based tests)
- Microservices:
- service-name/ (each service: own module/repo, own Dockerfile, Helm chart)
- shared-libs/ (common libraries maintained with versioning)

## Implementation contract (short)
- Inputs: HTTP/gRPC requests, async messages, DB events.
- Outputs: HTTP/gRPC responses, domain events, DB writes, metrics, logs, traces.
- Error modes: transient infra failures (handled by retries & backoff), validation errors (client 4xx), auth/permission (401/403).
- Success criteria: automated build + tests, deployable container image, passing health checks, and basic load test within target SLOs.

## Developer UX & local dev setup
- Provide a docker-compose file to bring up Postgres, Kafka (or RabbitMQ), Redis, and Keycloak for local development.
- Use dev profiles (Spring profiles or Quarkus config) to switch between in-memory/mocked dependencies and real infra.
- Prefer devtools / hot-reload (Spring DevTools, Quarkus dev mode) for fast feedback.

## CI/CD pipeline outline
1. Checkout code and run static analysis (spotbugs, checkstyle, dependency-check).
2. Build with Maven/Gradle and run unit tests.
3. Run integration tests using Testcontainers (or a test environment).
4. Build Docker image and run a lightweight smoke test.
5. Push image to registry and create an immutable version/tag.
6. Deploy to staging via Helm or GitOps, run end-to-end and contract tests.
7. Promote to production with a controlled rollout (canary, blue/green).

## Security checklist
- Enforce HTTPS everywhere (nginx/ALB + service TLS).
- Use OAuth2/OIDC for authentication; never roll your own auth.
- Validate and sanitize inputs; use parameterized queries or ORM to prevent SQL injection.
- Short-lived JWTs + refresh tokens; rotate secrets stored in Vault.
- Apply principle of least privilege for service accounts and database credentials.
- Use static analysis and dependency vulnerability scanning (Snyk, Dependabot, or OWASP Dependency-Check) in the pipeline.

## Observability & SLOs
- Capture structured logs (JSON) with request IDs.
- Export traces and metrics via OpenTelemetry libraries to Jaeger and Prometheus.
- Define SLOs (error rates, latency P95/P99) and set up Grafana dashboards and alerts.

## Testing strategy
- Unit tests for business logic (JUnit 5 + Mockito), aim for fast execution.
- Integration tests using Testcontainers to run Postgres/Kafka in CI for realistic integration.
- Contract tests (Consumer-Driven Contracts) to protect service boundaries.
- End-to-end smoke tests after deployment to staging.
- Load testing in staging with real-ish data using Gatling/k6.

## Performance tips
- Prefer connection pooling (HikariCP), efficient query patterns, and proper indexing for Postgres.
- For high concurrency paths, consider reactive stacks (R2DBC, WebFlux, Mutiny in Quarkus) and benchmark carefully.
- Use caching for read-heavy endpoints; measure cache hit ratio and TTLs.
- Profile hot paths using async-profiler / JFR and iterate.

## Minimal MVP (first milestone)
1. Core domain module with a single bounded context (e.g., Orders, Users, Inventory) implemented in a modular monolith.
2. REST API with OpenAPI docs and basic CRUD flows.
3. Postgres persistence with Flyway-managed schema.
4. Dockerfile and docker-compose for local dev (Postgres + Redis + Keycloak minimal).
5. CI pipeline with build, unit tests and a basic integration stage.
6. Logging, health endpoints, and a Prometheus metrics endpoint.

## Roadmap & next steps
- Phase 1: Create modular monolith with domain-first design + baseline CI and infra (DB, cache).
- Phase 2: Add observability (traces, metrics, dashboards) and security (Keycloak integration).
- Phase 3: Introduce asynchronous messaging and eventing for selected flows.
- Phase 4: Split off the first microservice from monolith (bounded context extraction) and deploy to Kubernetes.
- Phase 5: Harden CI/CD, rollout strategies, and secrets management.

12 changes: 12 additions & 0 deletions app/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Multi-stage Dockerfile
FROM maven:3.9.4-eclipse-temurin-17 AS build
WORKDIR /workspace
COPY . /workspace
RUN mvn -B -DskipTests package -pl app -am

FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=build /workspace/app/target/*.jar /app/app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

55 changes: 55 additions & 0 deletions app/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.example</groupId>
<artifactId>java-microservices</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>app</artifactId>
<packaging>jar</packaging>

<name>app</name>

<properties>
<spring-boot.version>${spring-boot.version}</spring-boot.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.example</groupId>
<artifactId>core</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>com.example</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

12 changes: 12 additions & 0 deletions app/src/main/java/com/example/app/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.example")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.example.app.controller;

import com.example.common.dto.GreetingDto;
import com.example.core.service.GreetingService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

private final GreetingService greetingService;

public GreetingController(GreetingService greetingService) {
this.greetingService = greetingService;
}

@GetMapping("/api/greet")
public GreetingDto greet(@RequestParam(required = false) String name) {
String message = greetingService.greet(name);
return new GreetingDto(name, message);
}
}

7 changes: 7 additions & 0 deletions app/src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
server:
port: 8080

spring:
main:
allow-bean-definition-overriding: false

28 changes: 28 additions & 0 deletions app/src/test/java/com/example/app/GreetingControllerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.app;

import com.example.common.dto.GreetingDto;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.web.server.LocalServerPort;

import static org.assertj.core.api.Assertions.assertThat;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class GreetingControllerTest {

@LocalServerPort
private int port;

@Autowired
private TestRestTemplate restTemplate;

@Test
void greetEndpoint() {
GreetingDto resp = this.restTemplate.getForObject("http://localhost:" + port + "/api/greet?name=Bob", GreetingDto.class);
assertThat(resp).isNotNull();
assertThat(resp.getMessage()).isEqualTo("Hello, Bob");
}
}

33 changes: 33 additions & 0 deletions common/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>com.example</groupId>
<artifactId>java-microservices</artifactId>
<version>0.0.1-SNAPSHOT</version>
</parent>

<artifactId>common</artifactId>
<packaging>jar</packaging>

<name>common</name>

<dependencies>
<!-- Jackson is available via Spring Boot in app, but add a small dependency if standalone testing is desired -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.0</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

31 changes: 31 additions & 0 deletions common/src/main/java/com/example/common/dto/GreetingDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.example.common.dto;

public class GreetingDto {
private String name;
private String message;

public GreetingDto() {
}

public GreetingDto(String name, String message) {
this.name = name;
this.message = message;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

Loading
Loading