Preparing your learning space...
17% through Advanced FDE Skills tutorials
Real enterprise work means fitting your solution into a landscape of legacy systems, strict compliance rules, and dozens of stakeholders — and then connecting it to everything else reliably, at volume. This tutorial combines enterprise architecture fundamentals with the design of large-scale integrations.
An enterprise system is any software landscape that supports a large organization: ERPs, CRMs, billing platforms, data warehouses, internal tools. "Complex" means many moving parts, many owners, and real consequences when things break.
They typically share these traits:
Why it matters for an FDE: you can't just "ship a feature" — you have to fit your solution into an existing machine.
A typical enterprise has:
| Layer | Examples |
|---|---|
| Systems of record | SAP, Oracle, Salesforce, custom databases |
| Integration layer | APIs, message queues, ETL pipelines |
| Shared services | Auth (SSO), logging, notifications |
| Frontline apps | Internal tools, customer portals |
| Data layer | Warehouses, lakes, reporting |
Your solution is almost never standalone. It will read from some systems, write to others, and be observed by security teams.
Enterprise Architecture (EA) is the practice of designing the overall structure of an organization's systems, processes, and technology so they evolve in a coordinated way instead of chaotically.
Think of it as city planning for software: individual buildings (applications) must follow zoning rules (standards), connect to shared roads (integrations), and fit a long-term map (target architecture).
Most EA frameworks describe four connected layers:
Business: "Customer must receive an invoice within 24 hours of purchase" Application: Order Service → Billing Service → Notification Service Data: orders table, invoices table, customer contact data Technology: AWS, PostgreSQL, SQS, email provider API
The explanation: a single business sentence cascades into concrete decisions at every layer. When a requirement is unclear, walk up the layers; when a technical choice is unclear, walk down.
Presentation, business logic, and data are separated. Simple to reason about, common in legacy systems.
Systems expose services over the network and evolve independently.
# Example service boundaries for an order platform
services:
- orders # owns order lifecycle
- payments # owns money movement
- inventory # owns stock levels
- notifications
The explanation: each service owns its data and can be deployed without touching the others. The boundary choice (who owns what) matters more than the technology.
Services react to events instead of calling each other directly. Great for loose coupling.
# Producer publishes, consumers react independently
bus.publish("order.placed", {"order_id": 123, "total": 49.99})
Every capability is exposed through a documented, versioned API so other teams can build on it without direct database access.
Functional requirements say what the system does. Non-functional requirements (NFRs) say how well it does it — and in enterprises, NFRs often decide whether your solution is accepted at all.
Key ones:
# NFR section of a design doc (practical template) Availability: 99.9% monthly Latency: p95 < 300ms for read APIs Security: OAuth2 via corporate SSO, data encrypted at rest Audit: All writes logged to immutable audit store
The explanation: put these numbers in writing early. "It should be fast and secure" is not a requirement; a number is.
Legacy systems are the norm, not the exception. Practical rules:
# Anti-corruption layer: translate a messy legacy format
# into a clean internal model once, at the boundary.
def from_legacy_record(raw: dict) -> "Customer":
return Customer(
id=int(raw["CUST_NO"]),
name=f"{raw['F_NAME'].strip()} {raw['L_NAME'].strip()}",
status={"A": "active", "I": "inactive"}[raw["STAT_CD"]],
)
The explanation: the legacy quirks stay inside one translation function. The rest of your codebase never sees STAT_CD and never has to care.
In enterprise work, architecture decisions fail socially more often than technically.
# A one-page design doc skeleton that works everywhere 1. Problem & business value 2. Proposed architecture (one diagram) 3. Data flows & integrations 4. NFRs (availability, latency, security) 5. Rollout plan & rollback plan 6. Open questions
An integration is "large-scale" when any of these are true:
The core skill: assume everything will fail, and design so failures are recoverable.
| Style | Best for | Trade-off |
|---|---|---|
| Synchronous API calls | User-facing, needs instant answer | Couples availability |
| Webhooks | Third parties push updates to you | Needs retry + verification |
| Message queues | High volume, async workflows | Eventual consistency |
| Batch / file exchange | Legacy systems, bulk data | Delayed data |
Pick the simplest style that meets the requirement. Don't build streaming infrastructure for a once-daily report.
When consuming or exposing APIs at scale:
import requests
def fetch_customers(page: int):
resp = requests.get(
"https://partner.example.com/v1/customers",
params={"page": page, "per_page": 200},
headers={"Authorization": f"Bearer {get_token()}"},
timeout=10,
)
resp.raise_for_status()
return resp.json()
The explanation: pagination handles volume, a short timeout prevents hangs, and raise_for_status turns HTTP errors into exceptions you can retry on.
Key principles:
/v1/, /v2/) and never break an existing version silently.429 responses and Retry-After headers.Webhooks are HTTP callbacks: when something happens at the provider, they call your endpoint.
import hmac, hashlib
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
WEBHOOK_SECRET = b"shared-secret"
@app.post("/webhooks/partner")
async def partner_webhook(request: Request):
signature = request.headers.get("X-Signature", "")
body = await request.body()
expected = hmac.new(WEBHOOK_SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
raise HTTPException(status_code=401)
event = parse(body)
await queue.enqueue(event) # acknowledge fast, process async
return {"status": "accepted"}
The explanation: verify the signature (so fake events can't inject data), respond quickly, and push the real work into a queue. If processing is slow, the provider will time out and retry, causing duplicates.
For bulk data (nightly syncs, partner feeds):
import csv, gzip, hashlib
def write_daily_export(records, path: str):
with gzip.open(path, "wt", newline="") as f:
writer = csv.DictWriter(f, fieldnames=["id", "email", "status"])
writer.writeheader()
writer.writerows(records)
def file_checksum(path: str) -> str:
return hashlib.sha256(open(path, "rb").read()).hexdigest()
The explanation: compress large files, attach a checksum so the receiver can detect corruption, and always include a manifest (row count, timestamp) so partial files are caught.
Queues decouple producers from consumers and absorb traffic spikes.
# Consumer with idempotent processing
def handle_message(msg):
if already_processed(msg.id): # idempotency check
return
with db.transaction():
apply_event(msg.payload)
mark_processed(msg.id)
The explanation: queues guarantee "at least once" delivery, which means duplicates. The idempotency check plus transactional write makes processing safe to run twice.
These three patterns are the backbone of reliable integrations.
import time, random
def call_with_retry(fn, retries=5):
for attempt in range(retries):
try:
return fn()
except TransientError:
if attempt == retries - 1:
raise
time.sleep((2 ** attempt) + random.random())
Only retry transient failures (timeouts, 503s). Never retry a 400 — the request itself is broken.
resp = requests.post(
"https://payments.example.com/v1/charges",
json={"amount": 4999, "customer": "cus_123"},
headers={"Idempotency-Key": "order-8842-charge"},
)
The explanation: if the first request's response is lost and you retry, the provider recognizes the key and returns the original result instead of charging twice.
class CircuitBreaker:
def __init__(self, failure_threshold=5, reset_after=30):
self.failures = 0
self.opened_at = None
def call(self, fn, *args):
if self.is_open():
raise CircuitOpen("Partner API is down — failing fast")
try:
return fn(*args)
except TransientError:
self.failures += 1
raise
The explanation: after repeated failures, stop calling the dead service and fail fast. This prevents your thread pools from filling up while you wait on a system that isn't coming back soon.
The messy middle of every integration. Rules that save weeks:
MAPPING = {
"partner_ref": "external_id",
"email_addr": "email",
"acct_status": "status",
}
def map_record(raw: dict) -> dict:
out = {MAPPING[k]: v for k, v in raw.items() if k in MAPPING}
if not out.get("email"):
raise InvalidRecord(raw, "missing email")
return out
At scale, "it worked in testing" is meaningless. You need:
-- Nightly reconciliation: orders sent but never confirmed
SELECT o.id, o.sent_at
FROM orders_sent o
LEFT JOIN partner_confirmations c ON c.order_id = o.id
WHERE c.order_id IS NULL
AND o.sent_at < NOW() - INTERVAL '24 hours';
The explanation: reconciliation catches the silent failures — dropped webhooks, lost files — that logs alone never reveal.
Save your progress and earn XP for completing tutorials.
4 questions · Pass with 70%+
1Which of the following is a well-written non-functional requirement (NFR)?
2Your codebase must consume a legacy system that returns messy records like {"F_NAME": "...", "STAT_CD": "A"}. What is the recommended approach?
3A partner API returns a 400 Bad Request. What should your integration do?
4Your logs show no errors, but partners report missing data and you suspect a dropped webhook. What mechanism catches this class of silent failure?
Technology
Forward Deployed Engineer
Lesson group
Advanced FDE Skills
Progress
17% complete