Phase 7 · Advanced & PortfolioModule 41~65 min read

Architecture for Growing Systems

Shape maintainable boundaries with modular monoliths, clean architecture, DDD building blocks, and deliberate distributed systems.

What you'll learn

Grow a PHP system without prematurely distributing it. Modular monoliths, clean dependency direction, domain modeling, commands, queries, and domain events create strong boundaries that can evolve before network boundaries become necessary.

By the end of this lesson, you'll be able to:

  • Design a modular monolith
  • Apply clean architecture dependency direction
  • Use DDD building blocks and events selectively

Core mental model

Professional PHP is less about memorizing APIs and more about choosing a clear boundary for each responsibility. Use this table as a decision guide while reading the examples.

ConceptWhat it protectsDecision rule
ModuleChange ownership boundaryGroup a cohesive business capability with an explicit public API.
Application portDependency inversionCore use cases define what infrastructure must provide.
Domain eventCross-module factPublish a completed business fact after durable state change.

Professional workflow

Build the feature in small, verifiable steps. Each step leaves the system in a state you can test.

  1. Describe the system architecture boundary boundary: its inputs, outputs, invariants, and expected failures.
  2. Implement the smallest happy path behind an explicit contract.
  3. Add validation and translate low-level failures into language the caller understands.
  4. Exercise the boundary with realistic data, then inspect output, logs, and resource cleanup.
  5. Refactor only after behavior is protected by a repeatable check.

Make the boundary visible

Name inputs, outputs, side effects, and failure cases before adding framework or infrastructure code. That habit keeps advanced PHP understandable as the application grows.

Guided code lab

Expose one module use case

The application service depends on module-owned ports; transport and PDO adapters remain outside.

EnrollStudent.php
<?php
namespace Academy\Enrollment\Application;

final readonly class EnrollStudent
{
    public function __construct(
        private Enrollments $enrollments,
        private Courses $courses,
        private EventPublisher $events,
    ) {}
    public function handle(EnrollStudentCommand $command): void
    {
        $course = $this->courses->get($command->courseId);
        $enrollment = Enrollment::begin($command->studentId, $course);
        $this->enrollments->save($enrollment);
        $this->events->publish(...$enrollment->releaseEvents());
    }
}

Make module rules executable

A dependency test prevents accidental imports from another module's internals.

architecture.txt
Allowed:
  Enrollment/Application -> Enrollment/Domain
  Enrollment/Infrastructure -> Enrollment/Application
  Catalog/Application -> SharedKernel

Forbidden:
  Enrollment/Domain -> Laravel, PDO, HTTP
  Catalog/* -> Enrollment/Infrastructure
  SharedKernel -> any business module

Production practice

Contract

Modules own their data and public operations; cross-module access uses declared APIs or events, not table joins from anywhere.

Verification

Add architecture tests, module-level use-case tests, and contract tests for synchronous or event interfaces.

Operations

Trace cross-module workflows, version event schemas, and design repair paths for eventual consistency.

Common failure mode

Splitting into microservices before boundaries and operational maturity exist replaces in-process complexity with networks, deployments, duplication, and partial failure.

Independent workshop

Re-architect the course platform as Catalog, Enrollment, Billing, and Identity modules with one end-to-end purchase flow.

Your finished workshop must include:

  • Context map and dependency rules
  • Module APIs plus event contracts
  • Decision record comparing modular monolith and services

Definition of done

Run the happy path and at least two failure paths, explain one design tradeoff in a short README, and leave the code formatted and ready for review.

Recap & quick check

Key takeaways

  • Architecture controls dependency direction.
  • Modules align code with business capabilities.
  • Domain models protect meaningful invariants.
  • Distribution is an operational tradeoff, not an upgrade badge.

Quick check

1. Where should framework dependencies point?

2. What is a modular monolith?

3. When are microservices justified?

Keep the workshop: later phases deliberately build on these boundaries, so today's small example can become part of your portfolio architecture.