FACTORS OF CLOUD NATIVE APPLICATION & IMPLEMENTATION
src: rajeevkuruganti.com/2023/12-factors-of-cloud-native-applications
Cloud Native Application
A Cloud Native Application is an application that was developed with
the cloud principles of: robustness, scalability, easy integration, portability, and administration
into its design.
When an application adheres to the below factors, it fully leverage the benefits of cloud services
01
Single Codebase
One repo tracked in version control, many deploys
▶ Implementation
Use a Version Control System (VCS). One repo builds once
and the same artifact deploys to dev, QA, and production environments.
Shared code is extracted as a library.
# One repo → one artifact → all envs
git init my-service
git remote add origin https://github.com/org/svc
# Build ONCE, deploy to any env
./gradlew build # → app.jar
# Same JAR to dev / staging / prod
Tools: GitHubGitLabBitbucketSVN
02
Dependencies
Explicitly declare and isolate all dependencies
▶ Implementation
Declare every library with an exact pinned version in the build file. Never assume a package exists on the host OS. Build tool downloads and isolates all dependencies.
<!– Maven pom.xml — pin exact versions –>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>3.2.1</version> <!– exact! –>
</dependency>
Tools: MavenGradlenpm / package-lockpip freeze
03
Config
Store config in the environment, not the code
▶ Implementation
Database connection properties, LDAP servers, API keys — anything that differs per environment must never be in source code. Read from env vars or an external config service at runtime.
# application.yml — read from env vars
spring.datasource.url: ${DB_URL}
spring.datasource.username: ${DB_USER}
spring.datasource.password: ${DB_PASS}
# Inject via Kubernetes Secret
env:
– name: DB_URL
valueFrom.secretKeyRef: db-secret
Tools: K8s ConfigMapK8s SecretHashiCorp VaultSpring Cloud Config
04
Backing Services
Treat as attached, swappable resources
▶ Implementation
App code must be identical whether using a local or cloud DB, queue, or S3. Connection details come from config (Factor 3). Swap any service by changing config only — zero code changes.
// Connection injected from config — local
// or cloud treated identically in code
DataSource ds = DriverManager.getConnection(
System.getenv(“DB_URL”), // from config
System.getenv(“DB_USER”),
System.getenv(“DB_PASS”)
);
// swap local PG → RDS: only env var changes
Tools: PostgreSQLRedisRabbitMQS3 / LDAP
05
Build, Release, Run
Three strictly separated pipeline stages
▶ Implementation
Build: compile → JAR/Docker image. Release: image + env config (immutable, versioned). Run: start the release. Never modify code at run-time.
# CI/CD pipeline stages
build: ./gradlew build # → app.jar
docker build -t app:$SHA .
release: docker push registry/app:$SHA
helm package ./chart –version $SHA
run: kubectl set image deploy/app \
app=registry/app:$SHA
Tools: JenkinsGitLab CIHarnessDockerHelm
06
Processes
Stateless, share-nothing microservices
▶ Implementation
Each microservice is a stateless process. Never use in-memory HTTP sessions — if the container restarts, all session data is lost. All persistent state lives in a backing service (Redis, DB).
// ❌ WRONG — in-memory session
HttpSession s = request.getSession();
s.setAttribute(“cart”, cart);
// ✅ CORRECT — externalize to Redis
@Autowired RedisTemplate<String,Cart> r;
r.opsForValue().set(“cart:”+userId, cart);
// survives container restarts
Tools: RedisHazelcastJWT (stateless auth)
07
Port Binding
Export services via configurable port binding
▶ Implementation
Never hardcode
port numbers in code or inter-service calls. PaaS/Kubernetes assigns ports at deploy time.Use service names for discovery.
Read port from env var with a default.
# application.yml — port from env
server.port: ${PORT:8080}
# Call other services by DNS name, not port
# ❌ http://10.0.1.5:9201/api/users
# ✅ http://user-service/api/users
# K8s Service exposes port abstractly
ports: [containerPort: 8080]
Tools: K8s ServiceCloud Foundryapplication.yamlEureka
08
Concurrency
Scale out via the process model (horizontal)
▶ Implementation
Scale by adding more stateless instances — not by
upgrading CPU and RAM. Design microservices to be small and cohesive so each scales independently.
Use HPA for automatic scaling.
# K8s Horizontal Pod Autoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
spec:
scaleTargetRef: {name: order-service}
minReplicas: 2
maxReplicas: 20
metrics: cpu avg utilization 70%
Tools: K8s HPAAWS Auto ScalingDocker Swarm
09
Disposability
Fast startup + graceful shutdown
▶ Implementation
App starts in seconds. On SIGTERM, finishes in-flight
requests then exits cleanly. Monitor
application health
so K8s readiness probe knows when app is truly ready.# Graceful shutdown (application.yml)
server.shutdown: graceful
spring.lifecycle.timeout-per-shutdown-phase: 30s
# K8s readiness + liveness probes
readinessProbe:
httpGet: {path: /actuator/health, port: 8080}
initialDelaySeconds: 10, periodSeconds: 5
Tools: Spring ActuatorK8s ProbesMicrometer
10
Dev/Prod Parity
Keep all environments as similar as possible
▶ Implementation
Use CI/CD for frequent deployments to minimise
the dev→prod delta. Use the same backing service type in all envs. Use testContainers or local Docker for dev to mirror prod services.
.
# GitLab CI — identical deploy per env
stages: [build, test, deploy-dev, deploy-prod]
deploy-dev:
script: helm upgrade app ./chart \
–set env=dev –set image.tag=$CI_SHA
deploy-prod:
script: helm upgrade app ./chart \
–set env=prod –set image.tag=$CI_SHA
Tools: JenkinsGitLab CIHarnessHelmTerraform
11
Logs
Treat logs as event streams to stdout
▶ Implementation
Write logs to stdout only — never to local files. When a container is destroyed, local files are gone. The platform (ELK, Splunk) captures and routes the stdout stream.
# application.yml — stdout, no file
logging.file.name: # EMPTY — no file
logging.pattern.console: “%d %-5level %msg%n”
// Java — standard logger to stdout
log.info(“Order {} shipped”, orderId);
// Logback/Log4j2 → stdout
// Platform: Fluentd collects → ELK/Splunk
Tools: ELK StackSplunkDatadogCloudWatchFluentd
12
Admin Processes
One-off management tasks run once, not on all pods
▶ Implementation
Scheduled jobs run in the same environment as the app. With multiple replicas, use a distributed lock so only one pod executes the job — not all replicas simultaneously.
// ShedLock — runs on exactly one instance
@Scheduled(cron = “0 0 8 * * ?”)
@SchedulerLock(name = “dailyNotify”,
lockAtMostFor = “PT10M”,
lockAtLeastFor = “PT5M”)
public void sendNotifications() {
// only one pod runs this
}
Tools: ShedLockQuartzK8s CronJob
13
Authentiction & Authorization
SSO for authn, role-based for authz
▶ Implementation
Use Single Sign-On (LDAP, Google Identity, Keycloak) for authentication. Define roles in LDAP or the app for authorization. Keep the two concerns strictly separate.
// Spring Security — OAuth2 SSO + roles
@Configuration
public class SecurityConfig {
@Bean SecurityFilterChain chain(HttpSecurity h) {
h.oauth2Login() // SSO (Google/LDAP)
.and().authorizeRequests()
.antMatchers(“/admin/**”).hasRole(“ADMIN”);
}
}
Tools: OAuth2 / OIDCKeycloakLDAPSpring Security
14
API First
Design API contract before implementation
▶ Implementation
Write the OpenAPI spec first. Front-end and back-end teams
work in parallel against the contract. Generate server stubs and client SDKs from the specifications.
# 1. Design OpenAPI spec first
paths:
/orders/{id}:
get:
summary: Get order by ID
responses:
200: {$ref: ‘#/components/schemas/Order’}
# 2. Generate server stub from spec
openapi-generator generate -i api.yaml \
-g spring -o ./generated
Tools:OpenAPI 3Swagger UIPostmanspringdoc-openapi
15
Circuit Breaker
Prevent cascading failures in distributed calls
▶ Implementation
When a downstream service fails,
auto-retry then open the circuit to stop requests flooding a broken service.
Provide a fallback.
States: Closed → Open → Half-Open.
States: Closed → Open → Half-Open.
// Resilience4J — minimal annotation usage
@CircuitBreaker(name=“paymentSvc”,
fallbackMethod=“payFallback”)
@Retry(name=“paymentSvc”)
public Payment processPayment(Order o) {
return paymentClient.pay(o);
}
Payment payFallback(Order o, Exception e) {
return Payment.queued(o); // graceful
}
Tools: Resilience4JSpring CloudIstio (service mesh)