When I first published ExtremePackaging, the goal was simple β to prove that highly modular Swift architectures can scale across platforms while keeping build times fast, dependencies isolated, and the mental model crystal clear.
But then came the next step: Could this same architecture host a complete OpenAPI workflow β with generated clients, servers, and full middleware integration β without losing its elegance?
Thatβs how the new reference implementation, swift-openapi-extremepackaging-example, was born.
π§© The Foundation β ExtremePackaging
The original ExtremePackaging repository introduced a clean, layered structure:
Shared packages for models and protocols
Independent feature modules for UI, data, and networking
No cross-package leaks
Unified Xcode workspace that felt like a monolith but built like microservices
The guiding philosophy: Each feature should be an island, communicating only through well-defined contracts.
That foundation made it ideal for integrating OpenAPI-generated code β which naturally fits into modular boundaries like SharedApiModels, ApiClient, and ApiServer.
π Evolving Toward OpenAPI
The OpenAPI version added an entire new layer of automation and functionality β transforming a static architecture into a living, self-describing API ecosystem.
1. OpenAPI Schema & Code Generation
At the heart of the project is the OpenAPI specification (openapi.yaml) β defining endpoints, models, and responses for a DummyJSON-compatible API.
Newly introduced elements:
π§± Full schema definitions for
Users,Posts,Products,Todos, andCartsπ§© Error schemas (404, validation, authentication)
βοΈ Integration with Swift OpenAPI Generator
π Automatic generation of clients, models, and server stubs
π¦ Separation of generated code into
SharedApiModels
This turned the architecture into a self-contained API ecosystem β one that can generate, serve, and consume its own endpoints.
2. API Server Implementation
A complete Vapor-based local server (ApiServer) was added to simulate real-world backend behavior:
17 endpoints fully implemented from the OpenAPI spec
Realistic mock data mirroring DummyJSON
Pagination and validation logic
Centralized error responses
π₯οΈ Prefixed logging for easy tracing in the console
The server runs locally at http://localhost:8080, serving as both a mock backend and a test harness for the generated client.
3. Enhanced API Client Architecture
The client evolved from a simple abstraction into a fully concurrent, actor-based networking layer.
Highlights
ApiClientactor manages shared state safely across async contextsMiddleware chain introduced: Logging β Authentication β Correction
Runtime environment switching between
.production,.local, and.mockShared singleton
ApiClientStatestores token, settings, and preferences
Example flow:
try await ApiClient.initializeShared(environment: .production)
let client = ApiClient.shared!
let auth = try await client.login(username: "emilys", password: "emilyspass")
await ApiClient.setToken(auth.accessToken)
let users = try await client.getUsers(limit: 10)
A clear separation between environment configuration and runtime state ensures deterministic, thread-safe behavior.
4. Middleware Integration
The client leverages two reusable middlewares from sibling packages:
OpenAPILoggingMiddleware Provides structured, console + JSON logging with full request/response capture.
BearerTokenAuthMiddleware Manages JWT token injection with a concurrency-safe actor and public operation rules.
Together, they demonstrate the power of middleware chaining in OpenAPI Runtime β clean, modular extensions without inheritance or global state.
5. YAMLMerger β The Key to Structured API Specs
The project uses YamlMerger β a Swift package that merges multiple YAML files into a single combined OpenAPI specification.
If your project doesnβt already include an openapi.yaml, YamlMerger ensures you have one β and helps you maintain a structured, predictable folder layout under Tests/ or Sources/SharedApiModels/schemas/.
π§ Why It Must Be Copied into the Project
YamlMerger cannot simply be added as a SwiftPM dependency for build-time merging because of SPMβs read-only resolution model:
Swift Package Manager stores dependencies in a cached, read-only location (
.build/checkouts/).The OpenAPI generator, however, needs write access to output the merged
openapi.yamlfile directly into your source tree.SPM build scripts are not allowed to write to source folders outside their sandboxed build directory.
β
Solution: Copy the YamlMerger executable directly into your project (e.g. Tools/YamlMerger/) and call it from a pre-build script or CI pipeline.
This guarantees write permissions and makes the tool available to everyone checking out the repo.
π§© What It Does
YamlMerger scans subdirectories (01 β 08) and merges YAML fragments in deterministic order:
Folders are processed numerically.
__*.yamlfiles merge first within each folder.Remaining files merge alphabetically.
The final output is a complete OpenAPI spec, suitable for Swift OpenAPI Generator.
π§± Example Schema Layout
Schema/
βββ 01_Info/
βββ 02_Servers/
βββ 03_Tags/
βββ 04_Paths/
βββ 05_Webhooks/
βββ 06_Components/
βββ 07_Security/
βββ 08_ExternalDocs/
Each folder corresponds to a section of the OpenAPI spec, allowing multiple developers to work on different endpoints, schemas, or components without conflicts.
βοΈ Typical Workflow
# Merge schemas before build
./Tools/YamlMerger merge --input Sources/SharedApiModels/schemas/ --output Sources/SharedApiModels/openapi.yaml
You can run this manually, in a pre-build phase, or as part of CI/CD automation.
π‘ Pro Tip
If your project starts without an openapi.yaml, placing schema fragments in structured folders under Tests/ ensures your API structure remains organized β even before full code generation.
YamlMerger gives your tests (and your teammates) a shared, visual map of your APIβs evolving shape.
6. Test Coverage Expansion
Two new test suites validate both local and production APIs:
π§ͺ
ApiClientLocalTests.swiftβ 25 tests targeting the local Vapor serverπ
ApiClientProductionTests.swiftβ 29 integration tests against DummyJSON API
Tests cover:
Authentication and token persistence
Pagination behavior
Error responses and invalid IDs
Concurrent request handling
Together they form a 54-test safety net proving both architecture and OpenAPI compliance.
π§ Architecture Snapshot
Packages/
βββ Sources/
β βββ ApiClient/
β βββ ApiServer/
β βββ SharedApiModels/
βββ Tests/
βββ ApiClientTests/
Each target is self-contained β just like in the original ExtremePackaging β but now with full OpenAPI integration, client/server symmetry, and end-to-end testability.
β‘ Key Improvements Over ExtremePackaging
| Area | Before | After |
|---|---|---|
| API Definition | Manual protocol layer | Generated OpenAPI spec |
| Networking | Custom client | Actor-based client w/ middlewares |
| Server | None | Vapor mock server (17 endpoints) |
| Authentication | Static token | BearerTokenAuthMiddleware |
| Logging | Simple print logs | Structured OpenAPILoggingMiddleware |
| Testing | Minimal unit tests | Full integration tests (54 total) |
| Schema Management | Handwritten | Modular YAML + YamlMerger |
| Tooling | Swift only | Swift + OpenAPI toolchain |
π§ Lessons Learned
OpenAPI fits perfectly into modular Swift architectures β generated code belongs in its own layer, and SwiftPM makes that separation effortless.
Actors are the future of shared state β simple, safe, and transparent.
Middleware > Managers β function composition scales better than class hierarchies.
Automation beats documentation β with OpenAPI, the spec is the documentation.
π¬ Closing Thoughts
This evolution of ExtremePackaging into a full OpenAPI reference app is more than a demo β itβs a blueprint for modular API-driven development in Swift.
From YAML schemas to live servers and typed clients, everything now exists in one unified, testable ecosystem β powered by Appleβs official OpenAPI tools and guided by the ExtremePackaging philosophy.
π Explore the project: swift-openapi-extremepackaging-example
βArchitecture should scale not by adding layers, but by removing assumptions.β
