Codebase Integration and Source Code Management
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
From Design To Code
The final handoff
Now that we've designed an e-commerce system in Real-World Project: E-Commerce System Design, the next question is practical:
How do multiple engineers safely turn that architecture into a working codebase without breaking each other?
This lesson connects everything we've covered:
- HLD becomes repositories, services, APIs, and deployment units.
- LLD becomes packages, interfaces, tests, and reviewable changes.
- Microservices become independently versioned codebases or modules.
- Data design becomes migrations and compatibility rules.
- Cloud architecture becomes CI/CD pipelines and environment-specific configuration.
Source code management is not just Git commands. It is the operating system for your engineering team.

Repositories Are Boundaries
Your repo layout reflects your architecture
In earlier lessons, we talked about service boundaries and ownership. Source control makes those boundaries visible.
For our e-commerce project, a reasonable structure might be:
ecommerce-platform/
services/
catalog-service/
cart-service/
order-service/
payment-service/
notification-service/
shared/
common-events/
test-support/
infrastructure/
docker-compose.yml
k8s/
docs/
architecture-decisions/
This is a monorepo-style layout: many services in one repository.
A polyrepo-style layout would put each service in its own repo:
catalog-service-repo
cart-service-repo
order-service-repo
payment-service-repo
Neither is automatically better. The question is: which layout makes ownership, testing, deployment, and change coordination clearer?

Guarding Code Boundaries
Architecture rules should be testable
A common failure in growing codebases is this:
The diagram says services are independent, but the code quietly imports internal classes across boundaries.
For example, order-service should call payment-service through an API or event contract. It should not directly depend on payment-service.internal.CardProcessor.
The small Java program below simulates a lightweight architecture check. In real projects, teams use tools like ArchUnit, Maven module rules, Gradle boundaries, or CI checks.
```java
import java.util.*;
public class ArchitectureBoundaryCheck {
static class Dependency {
String from;
String to;
Dependency(String from, String to) {
this.from = from;
this.to = to;
}
}
public static void main(String[] args) {
List<Dependency> dependencies = List.of(
new Dependency("order-service", "common-events"),
new Dependency("order-service", "payment-api"),
new Dependency("order-service", "payment-service.internal"),
new Dependency("catalog-service", "common-events")
);
Set<String> forbiddenTargets = Set.of("payment-service.internal", "catalog-service.internal");
boolean passed = true;
for (Dependency dependency : dependencies) {
if (forbiddenTargets.contains(dependency.to)) {
passed = false;
System.out.println("FAIL: " + dependency.from
+ " must not depend on " + dependency.to);
} else {
System.out.println("OK: " + dependency.from
+ " can depend on " + dependency.to);
}
}
System.out.println(passed ? "Architecture check passed" : "Architecture check failed");
}
}
```
Repo Strategy Check
A team has five tightly related Spring Boot microservices. They often update shared event contracts and run end-to-end tests across all services before deployment.
Which repository strategy is likely easiest to start with?
Branching Without Chaos
Branching is a team workflow, not a Git trick
A clean branching model helps code move from idea to production safely.
A practical default for many teams is trunk-based development:
main
├── short-lived feature branch
├── short-lived bugfix branch
└── release tag when production deploys
The key idea: branches should be short-lived. Long-running branches create painful merges and hide integration problems.
A healthy pull request usually contains:
- One focused change
- Tests or evidence of testing
- Clear description of user/system impact
- Screenshots or API examples when relevant
- Linked ticket or design decision when needed
Remember the design loop from the first lesson: design is not finished when the diagram is drawn. It continues through review, tests, deployment, and feedback.

CI As Safety Net
CI turns integration into a repeatable habit
Continuous Integration means every meaningful change is automatically checked.
For our e-commerce platform, a basic CI pipeline might run:
- Compile Java code
- Run unit tests
- Run architecture boundary checks
- Run contract tests between services
- Build Docker image
- Scan for obvious security issues
- Deploy to a staging environment
The point is not to make the pipeline fancy. The point is to catch broken assumptions early.
```java
import java.util.*;
public class CiPipelineSimulator {
static class Stage {
String name;
boolean passed;
Stage(String name, boolean passed) {
this.name = name;
this.passed = passed;
}
}
public static void main(String[] args) {
List<Stage> stages = List.of(
new Stage("Compile", true),
new Stage("Unit tests", true),
new Stage("Architecture boundary check", true),
new Stage("Contract tests", false),
new Stage("Build Docker image", true)
);
for (Stage stage : stages) {
System.out.println("Running: " + stage.name);
if (!stage.passed) {
System.out.println("Pipeline stopped at: " + stage.name);
System.out.println("Reason: integration contract is broken");
return;
}
System.out.println("Passed: " + stage.name);
}
System.out.println("Pipeline passed. Ready for deployment.");
}
}
```
Branching Truth
True or false:
Long-lived feature branches usually reduce integration risk because developers can work in isolation for weeks before merging.
Contracts And Migrations
Integration is more than compiling
A microservice can compile perfectly and still break production.
Two dangerous areas are:
1. API and event contracts
If order-service expects paymentStatus, but payment-service renames it to status, the system may fail at runtime.
2. Database migrations
If code expects a new orders.discount_code column but production has not migrated yet, checkout may fail.
A safer rule is:
Make changes backward-compatible first, deploy them, then remove old behavior later.
Example migration sequence:
Step 1: Add nullable column discount_code
Step 2: Deploy code that writes both old and new formats
Step 3: Backfill existing rows
Step 4: Deploy code that reads only the new format
Step 5: Remove old column or behavior later
This connects directly to the Change Data Safely idea from the data design lesson.

Compatibility In Code
Tiny compatibility check
Below is a simplified Java example that checks whether a new API response still contains the fields old clients require.
In real Spring Boot systems, this idea appears in contract tests, schema validation, OpenAPI checks, Pact tests, and consumer-driven contract testing.
```java
import java.util.*;
public class ApiCompatibilityCheck {
public static void main(String[] args) {
Set<String> oldRequiredFields = Set.of("orderId", "totalAmount", "paymentStatus");
Set<String> newApiFields = Set.of("orderId", "totalAmount", "paymentStatus", "estimatedDeliveryDate");
// Try replacing paymentStatus with status to simulate a breaking change.
List<String> missing = new ArrayList<>();
for (String field : oldRequiredFields) {
if (!newApiFields.contains(field)) {
missing.add(field);
}
}
if (missing.isEmpty()) {
System.out.println("Compatible: old clients can still read the response");
} else {
System.out.println("Breaking change detected. Missing fields: " + missing);
}
}
}
```
Missing Word
Fill in the blank:
A change is considered backward-__________ when existing clients can continue working without being updated immediately.
Course Wrap
You now have the full path
This course started with a simple question: how do we design systems that survive real-world complexity?
You now have a complete mental model:
Requirements
-> High-Level Design
-> Low-Level Design
-> Microservice Boundaries
-> Data Ownership
-> Cloud Deployment
-> Patterns and Best Practices
-> Real Project Design
-> Codebase Integration
For a Java/Spring Boot developer, the final lesson is important because architecture becomes real only when it is protected in the codebase.
Practical checklist
Before calling a system design "ready," ask:
- Is the repo structure aligned with service ownership?
- Are APIs and events versioned or contract-tested?
- Are database changes backward-compatible?
- Does CI run tests, builds, and architecture checks?
- Are pull requests small enough to review well?
- Can the team deploy safely to staging and production?
Great architecture is not only drawn. It is versioned, reviewed, tested, integrated, and continuously improved.
