Skip to main content
Case Study UniCycle Marketplace

Architectural Shift: MVC vs. Clean Architecture

An evolutionary case study comparing software design patterns. Evolved from a rapid prototype to a fully decoupled clean architecture, demonstrating the core fundamentals of software engineering: separating domain invariants, abstracting database drivers, and building testable business boundaries.

Role Full-Stack Developer
Timeline Fall 2025 (v1) / Winter 2026 (v2)
Core Fundamentals Separation of Concerns, Dependency Inversion, Clean Boundaries
Source GitHub
unicycle-marketplace/landing
UniCycle Marketplace public landing page
01 Origin & Inspiration

A Closed-Loop Campus Marketplace

UniCycle Marketplace was built to support safer campus trading for textbooks, electronics, and dorm items. Registration requires a .edu student address as an access gate. This case study documents the evolution of a web monolith into a more decoupled, testable system.

Project Type Personal Side Project & Architecture Lab
v1 Timeline (Fall 2025) Monolithic MVC Prototype
v2 Timeline (Winter 2026) Clean Architecture Refactoring
02 Lab Overview

The Shift from Feature Delivery to Architectural Discipline

UniCycle Marketplace was refactored across two engineering phases. Rather than just presenting final feature capabilities, this case study details how structural design priorities evolved when applying enterprise patterns to a monolithic codebase.

v1 Objective (Fall 2025) MVC & SPA Foundation

Engineering Scope: Express 5, EJS templating, and flexible document storage with MongoDB & Mongoose.

The Project Goal

Deliver a fully functional campus trading platform designed under the RUP (Rational Unified Process) framework. The priority was validating campus-email access and identity flows.

The Rationale

Velocity-first monolithic implementation. Express routes were coupled directly to database models to establish core CRUD operations, multi-file uploads, and template rendering within a unified codebase.

v2 Objective (Winter 2026) Applied Architecture

Engineering Scope: Decoupled layer boundaries, Repository pattern interfaces, and MySQL relational integrity.

The Design Objective

Isolate business rules from the execution environment. The goal was to draw strict boundaries so that domain validation and workflows remain independent of third-party libraries.

The Rationale

Decoupled, boundary-first design. Replaced MongoDB with MySQL to enforce referential constraints, and refactored request pipelines to run through Use Cases, DTOs, and testable repository mocks.

2
Database Adapters

MongoDB (v1) and MySQL (v2)

4
Architecture Layers

Entities, Use Cases, Interfaces, Adapters

0
Express DB Coupling

Controllers route requests through Repositories

Round 01 Persistence Strategy

Flexible JSON Documents vs. Strict Relational Mapping

How database choices impacted referential integrity and schema stability during development.

UniCycle v1 (MongoDB)

Dynamic JSON Collections

Stored users, categories, and listings as unstructured documents. Listings nested arrays of images directly inside the document.

The Why

To optimize query read performance. By nesting images and categories as JSON directly inside the listing document, the application could retrieve complete product details in a single database round-trip, completely avoiding the query latency and CPU overhead of multi-table SQL joins.

The Outcome

Poor referential safety. As the status workflow expanded (Draft, Pending, Active, Sold), maintaining consistent state between category deletion and user listings required writing manual hooks. Broken references were hard to prevent without database constraints.

UniCycle v2 (MySQL)

Relational Schema & Foreign Keys

Created formal tables with explicit data types, unique keys, and foreign keys mapping category and user relations.

The Why

To enforce real referential integrity. If an administrator deletes a category or user account, SQL constraints (`ON DELETE CASCADE`) guarantee that related listings and images are cleaned up.

The Outcome

Total relational safety. Database structures are strictly versioned via migrations (`migrate.js`, `schema.sql`). While it added structure mapping overhead, it completely eliminated silent dangling data references.

Round 02 Business Validation

Coupled Schema Rules vs. Framework-Free Domain Entities

Isolating system invariants and core logic from third-party database libraries.

UniCycle v1 (Mongoose)

Coupled Schema-Level Rules

Defined user validations (email format, password constraints) directly inside the Mongoose model schema definition in `models/User.js`.

The Why

Kept data validation and collection structures in one central model, making it easy to reject bad inputs when saving records to MongoDB.

The Outcome

Tight coupling. If I wanted to validate a user registration payload without connecting to a running MongoDB database, Mongoose would crash. Business rules were bound directly to the database library.

UniCycle v2 (Clean Domain)

Pure Domain Entities & DTOs

Implemented standalone domain classes (`models/User.js`) with zero database dependencies, alongside Data Transfer Objects (DTOs) for boundary sanitization.

The Why

To isolate system rules from infrastructure. DTOs sanitize request bodies, while pure domain entities validate system invariants (e.g. checking listing conditions) using standard JavaScript.

The Outcome

Decoupled domain. Domain models can be tested, loaded in command-line scripts, or moved to a different framework without needing database configurations or third-party schema engine connections.

Round 03 Controller Architecture

Fat MVC Monolith vs. Decoupled Use Case Orchestration

Evolving request handling from all-in-one scripts into clean, decoupled boundary interfaces.

UniCycle v1 (Monolith MVC)

All-in-One Controller Functions

Controllers imported Mongoose models directly, handling routing logic, authentication checks, schema queries, and view rendering in a single handler.

The Why

Provided the fastest path to connect EJS templates with MongoDB collections, reducing boilerplate and execution overhead in the early prototype iteration.

The Outcome

Severe logic duplication. Controllers had to know session formats, specific database queries, and templates. Changing the image upload path meant hunting down modifications across multiple router controllers.

UniCycle v2 (Clean Architecture)

Single-Responsibility Use Cases

Restructured code into layers: Controllers map parameters to DTOs and call Use Cases. Use Cases execute business logic via abstract Repository interfaces.

The Why

To ensure database and framework independence. Controllers only translate network requests, use cases orchestrate operations, and repositories handle storage.

The Outcome

High modularity. I successfully wrote repository adapters for both MongoDB and MySQL. Swapping persistence engines required editing just a single environment variable, proving the power of clean architectural boundaries.

Round 04 Verification Strategy

Integration Spin-Ups vs. Sub-Millisecond Mock Testing

Restructuring code parameters to test core business logic without network or database dependencies.

UniCycle v1 (Integration Tests)

Tied to the Database

Testing user authentication or listing creation required connections to a running MongoDB database instance or an in-memory database server.

The Why

Since Mongoose models and database connection instances were initialized directly inside controllers, there was no way to isolate logic checks from the database engine.

The Outcome

Slow, fragile tests. If the local database server had configuration issues, or the network Atlas connection timed out, tests failed. Developers ran tests less frequently due to high setup times.

UniCycle v2 (Mock Testing)

Pure In-Memory Interfaces

Tested Use Cases directly by injecting simple JavaScript mock classes (e.g. `MockUserRepository`) that simulate database returns using local arrays.

The Why

Because use cases only interact with abstract Repository interfaces, we can supply any object that satisfies the interface signatures during verification.

The Outcome

Blazing fast unit tests. All business rules (e.g. blocking suspended users from claiming listings, or checking limit counts) are verified in milliseconds with zero database or socket connections.

Round 05 File Reorganization

Evolution of the Directory Architecture

A side-by-side terminal comparison of the codebase layouts before and after drawing architectural layers.

UniCycle v1 - MVC Monolith Directory Architecture
unicycle-marketplace-v1/
├── config/
│   ├── db.js                        # MongoDB Connection
│   └── multer.js                    # Upload Middleware
├── controllers/                     # Fat Controller Handlers
│   ├── authController.js
│   ├── listingController.js
│   └── profileController.js
├── models/                          # Coupled Mongoose Models
│   ├── User.js
│   └── Listing.js
├── routes/                          # HTTP Routers calling Controllers
│   ├── authRoutes.js
│   └── listingRoutes.js
├── views/                           # UI Layout Templates
│   └── index.ejs
└── app.js                           # Entry Point & Middlewares
                    
UniCycle v2 - Clean Architecture Directory Architecture
unicycle-marketplace-v2/
├── app.js                                           # Bootstrapping Entry Point
├── package.json                                     # Scripts & Dependencies
├── src/
│   ├── domain/                                      # Enterprise Business Rules
│   │   ├── users/userRules.js
│   │   └── listings/listingRules.js
│   ├── application/                                 # Application Use Cases
│   │   ├── auth/authUseCases.js
│   │   └── listings/listingUseCases.js
│   ├── infrastructure/                              # Concrete Tech Adapters
│   │   ├── database/mysql/repositories/
│   │   ├── security/bcryptPasswordHasher.js
│   │   └── storage/localListingImageStorage.js
│   ├── interfaces/                                  # Express Delivery Layer
│   │   ├── web/controllers/
│   │   ├── web/routes/
│   │   └── web/dtos/CategoryDTO.js
│   ├── composition/                                 # DI Central Container
│   │   └── container.js                             # Composition Root
│   └── shared/                                      # Cross-Cutting Utilities
│       ├── errors/ApplicationError.js
│       └── result.js
└── test/                                            # Use Cases Unit Test Suite
    └── application/listingUseCases.test.js
                    
08 Feature VS Matrix

Evolution of Platform Capabilities

Comparing the implementation mechanics of core features between prototyping and clean architecture stages.

F01 User Authentication & Session State

v1 Implementation

Stack: bcrypt + express-session + Mongoose

The V1 Mechanism

Session variables are accessed directly in routes and controllers. User validation rules are declared inline inside Mongoose database schemas.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: bcrypt + express-session + UserRepository

The V2 Decoupled Boundary

Inputs map through UserDTO. Auth middleware references abstract Repository interfaces to isolate user session validation from database schema rules.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

F02 Listing CRUD Operations

v1 Implementation

Stack: Mongoose + EJS layouts

The V1 Mechanism

Controllers execute Mongoose CRUD operations directly, rendering the view and fetching MongoDB models inside Express handlers.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Domain Entities + Use Cases + MySQL Repository

The V2 Decoupled Boundary

Router maps inputs to ListingDTO. CreateListingUseCase coordinates domain validations and triggers the repository to persist relation changes.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

unicycle-marketplace/browse.C2f-12oR
Closed-loop marketplace board with compact search filters, condition badges, and discount pill tags.
Closed-loop marketplace board with compact search filters, condition badges, and discount pill tags.
unicycle-marketplace/detail.DH01JE_2
Listing detail view showing pricing, seller context, campus handoff guidance, saved-item controls, and the direct-message entry point.
Listing detail view showing pricing, seller context, campus handoff guidance, saved-item controls, and the direct-message entry point.

F03 Upload Review & Image Safety Flow

v1 Implementation

Stack: Multer + local storage checks

The V1 Mechanism

Multer saves files to uploads. Image paths are saved directly in listing documents; admins toggle visibility by modifying document arrays.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Staged Multer Uploads + Binary Signature Validation

The V2 Decoupled Boundary

Uploads are staged outside the public directory, checked against extension, MIME, and binary signatures, then promoted through storage and listing use-case boundaries.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

unicycle-marketplace/upload-review.BEa5e_iW
Student photo manager showing one public image, two private images awaiting review, accepted file formats, capacity, and upload guidance.
Student photo manager showing one public image, two private images awaiting review, accepted file formats, capacity, and upload guidance.
unicycle-marketplace/admin-approvals.C29sVosa
Split-pane admin moderation suite with sticky decision bar, listing inspection, and rejection reason workflows.
Split-pane admin moderation suite with sticky decision bar, listing inspection, and rejection reason workflows.

F04 Campus Email Access Gate

v1 Implementation

Stack: Mongoose schema and controller validation

The V1 Mechanism

Student email and access rules live beside persistence concerns, leaving registration behavior coupled to the document model and HTTP flow.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Domain Rule + Auth Use Case + UserRepository

The V2 Decoupled Boundary

The .edu address rule runs before persistence, while account uniqueness, roles, and status data remain behind repository and relational boundaries.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

F05 Notification & Event System

v1 Implementation

Stack: connect-flash only

The V1 Mechanism

State alerts are transient and coupled directly to HTTP redirects; no historical record of admin actions or listing updates is persisted.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Notification Repository + Event Trigger Flow

The V2 Decoupled Boundary

Persistent event logs generate real-time bell badges and in-app historical ledgers for listing reviews, support updates, and new message alerts.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

unicycle-marketplace/notifications.DT-Z7FiU
Persistent notification ledger connecting header badges to listing-review, support, and message events.
Persistent notification ledger connecting header badges to listing-review, support, and message events.

F06 Marketplace Messaging

v1 Implementation

Stack: No dedicated marketplace inbox

The V1 Mechanism

The prototype did not provide persistent listing-linked buyer and seller conversation threads inside the application.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Messaging Use Cases + Thread Repository

The V2 Decoupled Boundary

Students open conversations from a listing, retain message history and read state, and manage listing context through a dedicated split-pane inbox.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

unicycle-marketplace/messages.lx0fiy_j
Student inbox with listing-linked conversation context, persistent threads, read state, timestamps, and reply controls.
Student inbox with listing-linked conversation context, persistent threads, read state, timestamps, and reply controls.

F07 Support Triage & Listing Reports

v1 Implementation

Stack: One-way static inquiry contact form

The V1 Mechanism

Student reports and help inquiries are stored as static entries. No communication threads or listing-linked reports are supported.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Two-way Conversation Threads + Moderation Triage

The V2 Decoupled Boundary

Students submit reports directly from listings. Support tickets support multi-party replies and status triggers (Resolved/In-Progress).

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

unicycle-marketplace/admin-reports.C2I4bUBv
Moderation report workspace showing priority triage, deadlines, linked subjects, structured evidence, and account context.
Moderation report workspace showing priority triage, deadlines, linked subjects, structured evidence, and account context.

F08 Favorites & Bookmarks

v1 Implementation

Stack: Not implemented

The V1 Mechanism

Marketplace listings cannot be bookmarked or saved. Students have to manually search for previously viewed items.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Favorite Listing Join Table + Dashboard Widgets

The V2 Decoupled Boundary

Users toggle favorites directly from cards or details. Saved items are tracked via active joins and render in a dedicated, high-speed board.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

unicycle-marketplace/saved.Cyduw2TK
Dedicated saved-items board with persisted listing cards and direct remove-from-saved actions.
Dedicated saved-items board with persisted listing cards and direct remove-from-saved actions.

F09 Listing Routing & Slug Resolution

v1 Implementation

Stack: Numeric ID routes

The V1 Mechanism

Detail pages resolve strictly via database-level integer primary keys, exposing structural counters in URLs (e.g., /listings/13).

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Hybrid Title-Slugs with Legacy Redirects

The V2 Decoupled Boundary

Generates clean, human-readable SEO slugs (e.g., /listings/13-calculus) while transparently parsing numeric IDs and redirecting old formats.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

F10 Design System & Typography Architecture

v1 Implementation

Stack: Bootstrap 5 defaults + ad-hoc custom styles

The V1 Mechanism

Standard Bootstrap utilities and browser default fonts without centralized spacing tokens or consistent hairline border boundaries.

The V1 Lesson

Simple to implement but tightly coupled. Any modification required updating controllers, templates, and mongoose queries concurrently.

v2 Implementation

Stack: Space Grotesk + DM Sans + Flat Hairline Token System

The V2 Decoupled Boundary

Dual typography hierarchy (Space Grotesk display headings, DM Sans UI controls, System Mono metadata) paired with 0.5px hairline operational borders.

The Architectural Win

Business rules run independently. Use cases manage feature logic, controllers bridge HTTP requests, and repositories translate database operations.

unicycle-marketplace/admin-dashboard.Bm0blZS2
Administrative overview combining approval workload, open cases, recent listings, and staff-facing platform activity.
Administrative overview combining approval workload, open cases, recent listings, and staff-facing platform activity.
unicycle-marketplace/student-dashboard-content.HHNpmLYP
Student workspace combining seller metrics, recorded transactions, activity data, and listing-management actions.
Student workspace combining seller metrics, recorded transactions, activity data, and listing-management actions.
09 Lessons Learned

Architectural Journal Entries

Key takeaways and architectural insights from refactoring a server-side monolith into an isolated domain model.

Journal Entry

Relational Structure Rigor

Migrating to MySQL forced me to design data relationships and cascading constraints upfront. It proved that enforcing data integrity at the database layer prevents bad state errors from ever creeping into application code.

Journal Entry

Isolating Business Rules

By keeping the core domain logic free of Express and database driver imports, the system remains protected against third-party dependency updates. The core rules of the marketplace remain stable even if the delivery framework changes.

Retrospective

Engineering as a Craft

“Evolving UniCycle Marketplace from a working MVC prototype to a decoupled, testable Clean Architecture codebase wasn't just a requirement, it was a personal pivot. For me, software development is about more than just shipping features; it is about building systems that are durable, adaptable, and a pleasure to maintain. By applying enterprise boundaries to this codebase, I validated my approach to engineering: treating code not just as a tool, but as a craft.”
BC
Brian Cabello Software Developer

Let's build something that holds up.