Joining the Backend Team

Understanding the Existing Engineering Context

LuettavaEsikatselu

Katselet ilmaista esikatseluoppituntia.

So far, we have:

  • understood the Product requirement
  • separated functional and non-functional requirements
  • created User Stories
  • defined initial Acceptance Criteria

At this point, a beginner-level tutorial might start coding.

A real engineering team usually has another important step first:

What technical and organizational environment will this system live in?

Software is never built in a vacuum.

Even when creating a completely new backend application, there are usually existing:

  • platform constraints
  • technology conventions
  • deployment environments
  • security expectations
  • external dependencies
  • delivery practices
  • observability infrastructure
  • team conventions

If an engineer ignores that context, they may design something that is technically reasonable in isolation but does not fit the organization or production environment.

In this lesson, we will establish the engineering context for our Order Management Backend.

We are not adding new Product features.

We are defining the environment in which the already-agreed Product scope will be implemented.


You Rarely Start From Nothing

Imagine that you have just joined a company.

A Product Manager tells you:

We need a new Order Management Backend.

That usually does not mean:

Choose any programming language.

Choose any database.

Invent your own API conventions.

Deploy anywhere you want.

Create your own authentication mechanism.

Build your own logging infrastructure.

Use any build tool you prefer.

The company may already have established decisions.

For example:

Backend language:
Java

Framework:
Spring Boot

Database platform:
PostgreSQL

Source control:
Git

CI:
Existing company pipeline

Deployment:
Containerized applications

Authentication:
Existing identity capability

API style:
REST over HTTP using JSON

Observability:
Centralized logs and metrics

One of a new engineer's first responsibilities is to understand this environment.


Product Context vs Engineering Context

Our Product requirement says:

Customers should be able to place Orders.

That is Product context.

Engineering context might tell us:

The application must be implemented in Java.

The company uses PostgreSQL for transactional systems.

Applications are packaged as containers.

Authentication is provided by an existing identity capability.

Backend APIs use REST over HTTP with JSON.

The Product behaviour has not changed.

But the engineering environment constrains some implementation decisions.

This distinction is important:

Product Context
→ What behaviour do we need?

Engineering Context
→ What environment and constraints
  must the solution fit into?

Constraints Are Not Always Bad

The word constraint can sound negative.

But many constraints are valuable.

Suppose an organization says:

Backend applications use Java.

That removes some technical freedom.

But it may also provide:

shared expertise

shared libraries

consistent tooling

simpler support

common deployment practices

easier maintenance

Similarly:

Use PostgreSQL.

limits database choice.

But if the organization already operates PostgreSQL successfully, introducing a completely different database without a strong reason may create unnecessary operational cost.

Good engineering does not mean maximizing the number of available choices.

It often means:

Given the existing constraints, choose an appropriate solution.


Our Project Engineering Context

For this course, we will establish a stable engineering context for the Order Management Backend.

Unless a later requirement explicitly changes something, we will work within these boundaries:

Language:
Java

Framework:
Spring Boot

Build Tool:
Gradle

API Style:
REST over HTTP using JSON

Primary Database:
PostgreSQL

Database Migrations:
Flyway

Source Control:
Git

Testing:
JUnit-based automated testing

External Dependency:
Payment provider HTTP API

Authentication:
An existing identity capability provides
authenticated users

Deployment:
Containerized backend application

Observability:
Existing centralized logging and
metrics infrastructure

Let's examine what each decision means.


Java Is Already a Team Standard

The implementation language is:

Java

We are not going to begin by benchmarking Java against every other programming language.

There are two reasons.

First, this course assumes you are moving from Java and Object-Oriented Programming into backend development.

Second, in our simulated company context, Java is already an established backend technology.

This reflects a useful real-world lesson:

A new project does not automatically require a new programming-language decision.

When an organization already has a successful standard stack, using that stack is often the sensible default.


Spring Boot Is the Application Framework

Our team uses:

Spring Boot

for Java backend applications.

But Spring Boot is not the goal of this course.

The goal is:

Build a production-style
Order Management Backend.

Spring Boot is:

One of the tools we use to build it.

This distinction will remain important throughout the course.

We will learn Spring Boot capabilities because the project creates a need for them.


Why We Are Not Comparing Frameworks

We could spend time comparing:

Spring Boot

Quarkus

Micronaut

Jakarta EE

But our engineering context already establishes Spring Boot.

A framework comparison would not move this project forward.

Real engineering environments often eliminate many decisions before a feature even reaches a developer.

That can be beneficial.

It reduces unnecessary decision-making and helps teams maintain consistency.


Gradle Is the Existing Build Tool

Our project will use:

Gradle

Later, Gradle will handle tasks such as:

compile

test

package

dependency management

build automation

This is not a Gradle deep-dive course.

We will learn the parts required to work effectively on the backend application.


REST and JSON Are the API Convention

The company's backend applications conventionally expose HTTP-based APIs using:

REST-style resources

JSON

So a Product requirement such as:

Customer can create an Order.

will eventually be exposed through an HTTP API.

Something conceptually similar to:

POST /orders

may emerge later.

But we are not finalizing endpoint design in this lesson.

At this stage, the relevant engineering convention is simply:

HTTP + JSON + REST-style APIs

The actual API contract comes later.


PostgreSQL Is the Transactional Database

The primary persistent data store for the Order Management Backend is:

PostgreSQL

This is part of the existing engineering platform.

Our domain includes related concepts such as:

Products

Inventory

Orders

OrderItems

and later order-related payment information.

Order workflows also require multiple related state changes to remain consistent.

PostgreSQL gives us a strong transactional persistence foundation.

But we are not designing the database schema yet.


Database Choice and Database Design Are Different

We already know:

We use PostgreSQL.

We do not yet know:

Which tables should exist?

What relationships should they have?

Which constraints belong in the database?

Which indexes are necessary?

Where should transaction boundaries live?

Those are technical design decisions for later lessons.

Choosing a technology does not complete the system design.


Database Changes Must Be Versioned

Our production database schema will not be manually edited whenever the application changes.

Schema evolution must be:

versioned

reviewable

repeatable

We will use:

Flyway

for database migrations.

Later, our repository may contain files such as:

V1__create_products.sql

V2__create_inventory.sql

V3__create_orders.sql

The exact migration sequence will emerge from our implementation plan.

The principle is more important:

Database changes should be controlled and reproducible just like application code.


Authentication Is Not Our Identity Platform

The Product requirements distinguish between:

Customer

Administrator

because they have different permissions.

But our Order Management Backend will not implement a new identity provider.

The engineering context says:

An existing identity capability
authenticates users.

Our backend is responsible for:

receiving authenticated identity

determining allowed operations

enforcing resource ownership

enforcing permissions

Conceptually:

Existing Identity Capability
        ↓
Authenticated Identity
        ↓
Order Management Backend
        ↓
Authorization Decisions

Later, Spring Security will help us implement this boundary.


Why This Boundary Matters

Imagine that we started implementing:

password storage

password reset

email verification

account recovery

identity verification

inside the Order Management Backend.

The scope would expand dramatically.

But our responsibility is:

Order Management Backend

not:

Identity Platform

Maintaining clear system boundaries is a core backend engineering skill.


Customer Identity Is External Context

The Order Management Backend still needs to know:

Which Customer owns this Order?

But it does not need a full local Customer management subsystem to answer that.

Instead:

authentication layer
        ↓
stable authenticated user identity
        ↓
CustomerId used by Order workflows

Later, that CustomerId becomes part of application operations such as:

Create Order

View Own Orders

Cancel Own Order

Pay Own Order

The identity system owns authentication.

The ordering system owns the relationship between authenticated identity and Order data.


An External Payment Provider Already Exists

Our application does not perform actual card or payment-network processing itself.

The Product requirement already tells us that payment happens through an external provider.

Conceptually:

Customer
    ↓
Order Management Backend
    ↓
Payment Provider

Our responsibility includes:

initiate the appropriate payment interaction

handle provider responses

apply confirmed payment outcomes
to Order state

handle failures safely

prevent unintended duplicate processing

The provider owns the actual external payment execution.


Payment State vs Payment Processing

This distinction matters.

Our system needs to know when an Order has successfully become:

PAID

But it does not own the internal mechanics of:

card networks

bank authorization

provider settlement systems

Conceptually:

Payment Provider
→ owns external payment execution

Order Management Backend
→ owns the Order lifecycle consequence
  relevant to payment

We will refine this boundary when we implement external Payment integration.


External Systems Are Unreliable by Definition

The Payment provider may be highly reliable.

But communication across a network can still fail.

We need to consider possibilities such as:

timeout

connection failure

provider error

unexpected response

duplicate request

slow response

We are only identifying these risks now.

We are not implementing retry or idempotency policies yet.

Those belong in the external integration module.


Source Control Is Git

The team manages source code using:

Git

Git is not simply a backup mechanism.

It supports our development workflow:

ticket
   ↓
change
   ↓
commit
   ↓
Pull Request
   ↓
review
   ↓
merge

Ideally, the repository history should reflect incremental development rather than one enormous final change.


We Work Through Pull Requests

Changes are reviewed through Pull Requests before they are merged.

A reviewer may inspect:

implementation

tests

design impact

risk

documentation

Later in the course, we will simulate:

PR descriptions

code review

review feedback

follow-up changes

This matters now because it influences how we implement work.

We want:

small

coherent

reviewable

changes.


The Application Will Be Containerized

The deployment environment runs the backend as a containerized application.

Eventually:

Java Application
      ↓
Container Image
      ↓
Runtime Environment

We are not writing the Dockerfile yet.

But knowing the deployment model helps us avoid incorrect assumptions.

For example, the application should not treat its local filesystem as permanent business storage.


Application Instances Should Be Replaceable

In a containerized environment, an application instance may be:

started

stopped

replaced

restarted

Important business state should therefore not depend permanently on:

process memory

local application disk

Persistent business state belongs in appropriate durable storage such as:

PostgreSQL

This also gives us a better foundation if the application later runs with more than one instance.


Multiple Environments Exist

A real application does not run only on:

localhost

Conceptually, we may have:

Local Development
        ↓
Test / CI
        ↓
Non-Production
        ↓
Production

Those environments do not necessarily have identical infrastructure.

But the application must be configurable for different environments.

Later, we will use:

Spring configuration

environment variables

to accomplish that.


Configuration Must Not Be Hardcoded

Bad:

String databasePassword =
        "super-secret-password";

Also bad:

String paymentUrl =
        "https://production-payment.example";

hardcoded throughout the application.

Configuration such as:

database connection

Payment provider URL

credentials

environment-specific behaviour

must come from external application configuration.

Spring Boot will help us implement this later.


Secrets Are Not Application Code

Credentials, tokens, passwords, and similar secrets must not be committed into the source repository.

The principle is:

Application Code
≠
Secrets

We are not building a secret-management platform in this course.

But we will design the application so that sensitive values can be supplied through runtime configuration.


Centralized Logging Already Exists

The organization already has centralized logging infrastructure.

Our application's responsibility is:

produce useful logs

not:

build a centralized logging platform

Later, we will implement:

structured logging

request/correlation identifiers

useful error context

The distinction is:

Observability Infrastructure

versus:

Application Instrumentation

They are related, but they are not the same responsibility.


Metrics Infrastructure Already Exists

Likewise, the organization already has a way to collect metrics.

Our backend will eventually expose useful signals about:

request behaviour

application health

important failures

We will decide which metrics matter later.

We will not invent arbitrary dashboards or measure everything simply because a metrics platform exists.


Health Checks Are Expected

The deployment environment needs to determine whether the application is healthy.

Later, Spring Boot Actuator will help expose health information.

This is not a customer-facing Product feature.

But it is still a real production engineering requirement.

Product Feature?
No.

Production Requirement?
Yes.

This is exactly why engineering context matters.


CI Is Part of the Delivery Process

Before code is merged, an automated pipeline should at minimum be able to:

build

test

the application.

Later, we will implement CI.

For now, remember:

“Works on my machine” is not a completion criterion.

A change must also succeed in the team's automated environment.


We Are Building One Application

The first version of the Order Management Backend is:

one deployable backend application

Conceptually, it contains logical capabilities for:

Products

Inventory

Orders

Payment integration

Authenticated Customer identity enters through the security boundary rather than through a local Customer subsystem.

We are not creating separate microservices for these capabilities.


One Application Does Not Mean One Giant Package

A modular monolith does not mean:

all code in one enormous package

We will maintain clear logical boundaries.

Conceptually:

product

inventory

order

payment

Authentication/security concerns will integrate with these workflows without becoming a local Customer module.

The exact package structure comes later.

For now, the architectural boundary is:

one deployable backend application

Why Not Microservices?

A commerce domain may tempt us to create:

Product Service

Inventory Service

Order Service

Payment Service

Customer Service

But our current requirements do not justify that complexity.

Splitting the system into multiple services would introduce new problems:

network communication

distributed failures

deployment coordination

distributed transactions

service discovery

observability complexity

data ownership complexity

We do not need those problems yet.

Our goal is:

Build a production-quality backend foundation.

Distributed architecture is a separate engineering problem.


Technology Does Not Define Domain Boundaries

Using one application does not mean all concepts become the same thing.

Logical boundaries still matter.

For example:

Order domain logic

should not need to understand:

Payment provider HTTP details

Likewise:

Product domain logic

should not depend directly on:

controller JSON representation

We will make these boundaries concrete in later architecture and domain-modeling lessons.


Understand Existing Conventions Before Creating New Ones

Suppose an organization already uses an API error format like:

{
  "code": "RESOURCE_NOT_FOUND",
  "message": "Resource was not found"
}

A new engineer should not casually introduce:

{
  "oops": true,
  "reason": "not there"
}

The second response is technically valid JSON.

But it breaks platform consistency.

In a real team, engineers should inspect existing conventions before creating new ones.

Our course project is greenfield, but we will still establish and follow consistent conventions as it grows.


Existing Context Checklist

When joining a backend project, a useful discovery checklist includes:

Language and framework?

Build tool?

Repository conventions?

API conventions?

Authentication mechanism?

Authorization model?

Primary database?

Migration strategy?

Deployment environment?

Configuration management?

Secrets management?

Logging platform?

Metrics platform?

CI expectations?

Testing conventions?

External dependencies?

Ownership boundaries?

Not every question matters equally in every project.

But this checklist helps prevent context-free design.


Ownership Is Important

We should understand what our system owns and what external systems own.

Order Management Backend Owns

Product state required by the ordering domain

Inventory state

Orders

OrderItems

Order lifecycle

Order-related application state

Existing Identity Capability Owns

User authentication

Identity lifecycle

Payment Provider Owns

Actual external payment processing

We are not yet defining every persistence detail.

But the responsibility boundaries are clear.


Dependency Direction

At a system-context level:

Order Management Backend
        ↓
PostgreSQL
Order Management Backend
        ↓
Payment Provider
Authenticated Identity
        ↓
Order Management Backend

These arrows communicate dependencies and system relationships.

A more detailed architecture diagram will come later.

For now, the important lesson is that our application does not exist in isolation.


Team Context Matters Too

Engineering context is not only about technology.

Team practices matter.

Our simulated team works with principles such as:

Requirements are reviewed before implementation.

Significant design decisions are documented.

Work is broken into tickets.

Changes should be reviewable.

Automated tests are expected.

Pull Requests explain what changed.

Production readiness is part of delivery.

Incidents result in learning and corrective action.

This is one of the defining aspects of the course.

We will not only look at the final code.

We will follow the decision-making process that leads to it.


Documentation Should Match the Size of the Decision

We do not want documentation-heavy bureaucracy.

For a small change:

Ticket + Pull Request

may be enough.

For an important architecture decision:

ADR

may be appropriate.

For a larger cross-cutting design:

RFC

may be justified.

After a significant incident:

Incident Review

may be appropriate.

Different decisions deserve different levels of documentation.


Existing Context Can Override Personal Preference

Suppose you personally prefer Maven.

But every backend application on the team uses Gradle.

Using Gradle for the new application may still be the better engineering decision because of:

shared build tooling

shared CI

team familiarity

consistent maintenance

Engineering is not always about choosing your favourite technology.


Challenge Constraints When Necessary

Existing conventions are not automatically unquestionable.

Suppose a platform constraint genuinely prevents us from satisfying an important requirement.

Then the engineer should challenge it with evidence.

A useful discussion might look like:

Current approach cannot satisfy requirement X because Y.

Option A keeps the existing platform,
but has limitation Z.

Option B introduces a new technology
with these operational costs.

Recommendation:
...

But:

I like technology X better.

is usually not enough.


Greenfield Does Not Mean Constraint-Free

Our Order Management Backend is a new application.

But:

new application

does not mean:

no constraints

We are building a new service inside an existing company ecosystem.

That is a very common real-world scenario.


Questions We Can Now Answer

We can now answer several engineering questions.

Which language?

Java

Which framework?

Spring Boot

Which primary database?

PostgreSQL

Which migration mechanism?

Flyway

Which API communication style?

REST over HTTP using JSON

Which build tool?

Gradle

What is the deployment unit?

Containerized backend application

Does the Order backend implement full identity management?

No.

Does it implement actual external payment processing?

No.

It integrates with an external Payment provider.

Questions That Are Still Open

Even with the engineering context established, many design questions remain unanswered.

For example:

What exactly are our Domain Entities?

What should the package architecture look like?

Which API resources should exist?

What is the Order lifecycle?

What database schema should we use?

Where should transaction boundaries live?

How should errors be represented?

How should Payment retries work?

Which metrics are meaningful?

These belong to later modules.

We should not prematurely answer them here.


Context Prevents Scope Creep

Without clear boundaries, a Payment requirement could expand into:

payment gateway

fraud detection

card storage

refund engine

settlement system

But our engineering context says:

External Payment provider handles
actual payment processing.

That keeps our scope narrow.

Likewise:

Existing identity capability
authenticates users.

prevents us from accidentally building an account platform.

Boundaries protect scope.


Context Also Prevents Overengineering

Suppose we already know that the initial system is:

one deployable application

Introducing a message broker merely to replace a straightforward internal call may add complexity without solving a real problem.

Bad reasoning:

Kafka is scalable,
therefore we should use Kafka.

Better:

Do our current requirements require
asynchronous cross-system event delivery?

No.

Then do not add Kafka.

Technology should be justified by a requirement or constraint.


Our Initial System Context Diagram

At this stage, the system looks roughly like this:

                   ┌────────────────────────┐
                   │ Existing Identity      │
                   │ Capability             │
                   └────────────┬───────────┘
                                │
                                │ authenticated identity
                                ▼
┌──────────────┐      ┌────────────────────────────┐
│ Client       │─────▶│ Order Management Backend   │
└──────────────┘ HTTP │                            │
                      │ Java + Spring Boot         │
                      └──────────┬─────────┬───────┘
                                 │         │
                          persist│         │ payment interaction
                                 ▼         ▼
                        ┌────────────┐  ┌──────────────────┐
                        │ PostgreSQL │  │ Payment Provider │
                        └────────────┘  └──────────────────┘

This is not a detailed architecture diagram.

It is a system context diagram.

Its purpose is to answer:

What is inside our system?

What is outside our system?

What does our application depend on?

System Context Diagram vs Architecture Diagram

A system context diagram stays high-level.

It shows:

our system

external actors

external dependencies

A later architecture diagram may show concepts such as:

Handlers

UseCases

Domain

Repositories

PostgreSQL adapters

external integration Services

Do not mix those levels unnecessarily.


Context Before Design

Our progression now looks like:

Product Requirement
        ↓
User Behaviour
        ↓
Acceptance Criteria
        ↓
Engineering Context
        ↓
Technical Design

The sequence matters.

Technical design must satisfy both:

requirements

and:

engineering context

Example: Context Changes Design

Imagine the same Product requirement:

Customer can place an Order.

Context A:

Single Java application

PostgreSQL

Moderate traffic

Context B:

Existing distributed commerce platform

Multiple regions

Independent Inventory service

External Pricing service

The Product capability is identical.

The technical solution may be completely different.

This is why copying an architecture from a blog post without understanding its context is dangerous.


Ask: “What Already Exists?”

A powerful question for a new engineer is:

What already exists that I should integrate with instead of rebuilding?

For our project:

Identity capability exists.

Payment provider exists.

Logging infrastructure exists.

Metrics infrastructure exists.

Deployment platform exists.

We integrate with them.

We do not rebuild them.


Ask: “What Does Our Application Own?”

Another powerful question is:

What state and behaviour is this application responsible for?

For us, the answer is primarily:

the ordering domain

including:

Products

Inventory

Orders

OrderItems

Order lifecycle

If ownership is unclear, systems begin to overlap and responsibilities become difficult to maintain.


Ask: “What Is a Hard Constraint?”

Not every convention has the same weight.

For this project:

Java

Spring Boot

PostgreSQL

Gradle

containerized deployment

are established engineering-context decisions.

But things such as:

exact package names

exact Handler structure

exact Repository implementation

exact persistence mapping

are still technical design choices.

Knowing the difference prevents premature design.


Assumption Register

In a real project, not every piece of context may be completely confirmed.

An engineer can record assumptions.

For example:

ASSUMPTION-1

The external identity capability
provides a stable user identifier.
ASSUMPTION-2

The Payment provider exposes
an HTTP API.

For this course, we treat those as given engineering context.

In a real project, we might need to verify them.


Dependency Risk

An external dependency is both a capability and a risk.

Our major external dependency is:

Payment Provider

Potential risks include:

availability

latency

API changes

timeouts

duplicate processing

PostgreSQL is also a runtime dependency.

The identity capability is another dependency.

System design must eventually account for dependency failures.


We Do Not Need to Solve Dependency Failure Yet

We are not writing retry policies now.

We are simply identifying:

This operation crosses a system boundary.

That knowledge will guide later design.


Existing Context Influences Testing

The engineering environment also affects our testing strategy.

For example:

Java
→ JUnit ecosystem
PostgreSQL
→ real database integration testing matters
HTTP Payment provider
→ external integration boundary
  needs controlled testing
REST API
→ HTTP behaviour needs testing

Later, PostgreSQL integration tests will use an isolated real PostgreSQL environment rather than depending on a manually running developer database.


Existing Context Influences Deployment

Containerized deployment makes application lifecycle behaviour important.

For example:

Can the application start with valid configuration?

Can its health be checked?

Can it shut down safely?

Can database migrations be applied safely?

These concerns sit outside individual Product features but are still part of backend engineering responsibility.


Existing Context Influences Observability

Centralized logging does not mean:

System.out.println(...);

randomly throughout production code.

Application logs should be useful and machine-processable.

Likewise, having metrics infrastructure does not mean:

measure everything

We should expose signals that help engineers understand application health and important failures.

We will design those later.


Engineering Context Document

A lightweight repository document could capture our context:

# Engineering Context

## Application

Order Management Backend

## Language

Java

## Framework

Spring Boot

## Build Tool

Gradle

## API

REST / JSON over HTTP

## Persistence

PostgreSQL

## Database Migrations

Flyway

## Authentication

Provided by an existing identity capability.

## External Integrations

Payment provider over HTTP.

## Deployment

Containerized backend application.

## Delivery

Git, Pull Requests, automated CI.

## Observability

The application integrates with existing centralized
logging and metrics infrastructure.

This kind of document quickly communicates important project context.


Do Not Turn Context Into an RFC

We are still not writing a technical proposal.

Engineering context mostly records:

existing facts

existing constraints

existing boundaries

An RFC will later answer something different:

Given these requirements and this engineering context, how should we design the system?

That distinction matters.


What an Engineer Should Do on Day One

Imagine joining a real team.

Before immediately picking up a feature ticket, useful activities might include:

Read the Product context.

Run the existing application locally.

Understand the repository structure.

Read recent relevant Pull Requests.

Find architecture documentation.

Understand the deployment flow.

Understand testing conventions.

Identify external dependencies.

Understand ownership boundaries.

Ask questions where documentation is unclear.

This may not produce code immediately.

It is still valuable engineering work.


Codebase Is Also Documentation

When an existing system already exists, the code itself can provide context.

For example:

How are API errors represented?

Existing Handlers or exception mapping may answer that.

How are migrations named?

The migration directory may answer that.

How are tests structured?

Existing tests may answer that.

A good engineer inspects existing patterns before inventing new ones.


Existing Code Is Not Automatically Correct

Finding an existing pattern does not automatically mean it is the current recommended approach.

You may encounter:

duplicated logic

poor error handling

missing tests

outdated conventions

temporary workarounds

So the right question is not:

Does this pattern exist?

but:

Is this an intentional current convention?

Is it legacy technical debt?

Was it a temporary workaround?

What is the recommended pattern now?

Context discovery requires judgement.


Our Course Starts Greenfield but Simulates Team Context

Our learning repository starts from scratch.

We therefore cannot inspect an existing application on day one.

That is why we establish the team and platform context explicitly.

But as the course progresses, something important happens:

The code we write becomes the existing system.

By the time we reach the later change-request module, we are no longer working on a greenfield application.

We will have:

existing database schema

existing API contracts

existing tests

existing historical Orders

existing production behaviour

Changing the system safely will require understanding what already exists.

That evolution is intentional.


The Context Becomes More Important Over Time

At first, the application will be small.

The engineering context may feel simple.

Later, when a production change arrives:

existing database

existing clients

existing behaviour

existing tests

existing data

will constrain what we can safely change.

At that point:

just change the code

will no longer be a safe engineering strategy.

The course is designed to let you experience that transition.


Engineering Principle

The core principle from this lesson is:

A good technical solution is not merely correct in isolation. It must fit the system, team, constraints, dependencies, and operational environment around it.

Another useful way to express this is:

Requirements
      +
Engineering Context
      =
Inputs to Technical Design

Requirements without context produce incomplete design.

Technology context without Product requirements loses sight of the problem.

We need both.


Our Context Is Now Established

The starting engineering context for the Order Management Backend is now clear:

Java

Spring Boot

Gradle

REST / JSON over HTTP

PostgreSQL

Flyway

Git

Pull Requests

Automated CI

Containerized deployment

Existing identity capability

External Payment provider

Centralized logging and metrics

Our initial architecture boundary is:

One deployable backend application

And intentionally:

No microservices

No Kafka

No Redis

No distributed architecture

because the current requirements do not justify them.


What We Have Not Added

Engineering context does not expand Product scope.

We still have not added:

shipping

discounts

refunds

multiple warehouses

notifications

recommendations

event streaming

Engineering context defines the environment in which we implement the existing scope.

It does not invent new Product capabilities.


Where We Are in Module 1

Our journey is now:

Product Requirement
        ↓
Requirement Analysis
        ↓
Functional vs Non-Functional Requirements
        ↓
User Stories and Acceptance Criteria
        ↓
Engineering Context
        ↓
Definition of Done
        ↓
Initial Engineering Backlog

After the next two lessons, we will be ready to move from Product understanding into implementation planning.


Summary

In this lesson, we learned that:

  • Software is not built in isolation.
  • Product requirements and engineering context are different inputs.
  • Existing technology and platform constraints influence technical design.
  • Constraints are not automatically bad; consistency and operational simplicity have value.
  • A new project does not require every technology decision to be reopened.
  • Our Order Management Backend uses Java, Spring Boot, Gradle, PostgreSQL, Flyway, and REST/JSON.
  • Database changes will be versioned through Flyway migrations.
  • Authentication comes from an existing identity capability.
  • We are not building an identity platform or local Customer account-management subsystem.
  • The backend uses authenticated identity as Customer context for Order workflows.
  • Actual external payment execution belongs to the Payment provider.
  • The backend owns Order lifecycle behaviour related to confirmed Payment outcomes.
  • External dependencies introduce failure modes that must eventually be handled.
  • The application will run as a containerized backend service.
  • Application instances should not own permanent business state in local memory or local disk.
  • Environment-specific configuration must be externalized.
  • Secrets do not belong in source code.
  • Git, Pull Requests, automated testing, and CI are part of the delivery workflow.
  • Existing logging and metrics platforms are infrastructure; our application must provide useful instrumentation.
  • Health checks are production requirements even though they are not Product features.
  • The first version is one deployable modular application.
  • A modular monolith still requires clear logical boundaries.
  • Microservices, Kafka, Redis, and distributed architecture are not justified by the current requirements.
  • Existing conventions should be understood before new ones are invented.
  • Existing code is useful context, but it should not be copied blindly.
  • System ownership and external dependency boundaries should be clear before detailed design begins.
  • Requirements and engineering context together form the inputs to technical design.

Next lesson:

Definition of Done

There, we will define what it means for an engineering task to actually be complete—not merely that the code has been written, but that implementation, testing, review, documentation, and delivery quality meet the team's expected standard.