Reading mode

Case Study · HR & Assessment

GoPick

GoPick is an international HR assessment platform supporting candidates, assessments, evaluations, reporting, account management, and consumable report-generation credits. My work focuses on extending the existing platform while incrementally restructuring legacy areas into a more explicit modular architecture that is easier to trace, test, maintain, and change safely.

Placeholder for a custom project illustration representing an HR assessment platform undergoing incremental legacy modernization into a modular five-layer architecture.
Domain
HR & Assessment
My Role
Fullstack Engineer & DevOps Engineer
Lifecycle
Production · Active Development
Engineering Focus
Fullstack / DevOps / Architecture / Modernization
Core Stack
Yii2 (PHP) · MySQL · Redis · AWS
Public Links
Sanitized Case Study (Private Repository)
Provenance & Professional Relationship GoPick is a professional project associated with my experience at People Dynamics Inc. View Related Experience →

Platform Context & Public Modules

GoPick is an enterprise HR assessment platform engineered to support complex HR workflows. The system manages candidate evaluation cycles, assessment delivery, advisory evaluation workflows, report compilation, account management, consumable report-generation credits, and platform asset organization.

Where relevant to candidate evaluation integrity, anti-cheating and assessment safeguards are built into the platform workflow. This case study focuses exclusively on application architecture, structural engineering, and delivery practices. Confidential assessment algorithms, scoring logic, proprietary evaluation standards, and client data are strictly excluded.

GoPick Ecosystem

  1. Dashboard
  2. Account
  3. Candidate
  4. Assessment
  5. Test Norms
  6. Meters
  7. Reports
  8. users
  9. Role/Permission
  10. Activity log
  11. assessment account schedule
  12. email template
  13. emails
  14. advisory
  15. help desk
  16. test settings
  17. report templates
  18. resource center

My Role & Ownership Boundary

As a Fullstack Engineer and DevOps Engineer on GoPick, my contributions encompass significant work across legacy modernization, application architecture, feature development, system reliability, engineering documentation, feature delivery controls, developer testing, deployment workflows, and ongoing maintenance.

This case study does not claim ownership of the entire GoPick platform. Instead, it documents specific structural improvements, architecture standards, and critical subsystem features personally designed and implemented during active platform development.

System & Delivery Constraints

Architectural decisions on GoPick were directly shaped by realistic production constraints rather than unconstrained textbook ideals:

No Complete Rewrite A full top-to-bottom application rewrite was not an option. Active production use and ongoing feature requirements required continuous delivery without interrupting active workflows.
Database Schema Compatibility All architectural improvements had to remain 100% backward-compatible with the existing production MySQL database schema and existing data models.
Coexistence of Legacy and Modernized Code New explicit 5-layer components were required to run side-by-side with untouched legacy areas during the multi-phase transition.
Limited Existing Automated Test Coverage The baseline codebase had minimal automated unit/integration tests, making large-scale reckless refactoring a high-risk activity without localized guardrails.
Existing Development Processes Modernization efforts had to fit into ongoing team processes and release cycles without creating friction or disrupting feature commitments.
Infrastructure & Access Authority Boundaries Application-level architecture improvements were implemented within existing server and hosting parameters without reliance on infrastructure rewrites.

Legacy Before-State

The initial state of legacy areas contained typical characteristics of long-lived enterprise web applications where rapid feature growth preceded formal architectural boundaries. Responsibilities were loosely defined and frequently coupled across physical files.

That cost did not disappear. It accumulated as cognitive load for every new developer asked to trace behavior through implicit boundaries, side effects, and undocumented execution paths.

Common technical challenges in legacy areas included:

  • Business logic and execution branching embedded directly within controller actions.
  • SQL queries and database calls scattered through presentation views and controller scripts.
  • Monolithic classes and functions handling validation, data manipulation, rendering, and response logic simultaneously.
  • Duplicated query blocks and business rules across multiple endpoints.
  • High coupling and low execution traceability, where minor updates on one page could cause unexpected side effects elsewhere.
Mixed Responsibilities Single files contained combinations of raw SQL, PHP business operations, HTML presentation, JavaScript snippets, and inline CSS.
Low Traceability Tracing the exact data transformation flow required stepping through multiple implicit class calls and view includes. Different environments, different ghosts. feat. Multo - CoJ🎶
Touch it once, and you inherit the BLAST RADIUS. High Side-Effect Risk Lack of explicit boundary isolation meant changing a shared database query for one module risked breaking unassociated pages.

Incremental Migration Strategy

To modernize the application safely without disrupting ongoing operations, an opportunistic incremental refactoring strategy was adopted over an upfront rewrite.

The core policy follows three rules:

  1. Refactor Touch Points: When a bug fix, change request, or new requirement touches a legacy area, refactor the affected boundaries into the 5-layer architecture.
  2. Strict Greenfield Standard: All newly added modules and features must strictly adhere to the modular 5-layer structure from day one.
  3. Leave Untouched Legacy Stable: Untouched legacy regions remain operational until future work touches them, minimizing unnecessary regression risk.
Incremental migration strategy diagram for GoPick (light theme) Incremental migration strategy diagram for GoPick (dark theme)
Incremental Migration Strategy: Opportunistic refactoring flow transitioning legacy modules to the explicit 5-layer architecture.

Architecture - Modular 5-Layer Structure

The modernization introduces explicit responsibility ownership within each module boundary while establishing clean supporting shared layers:

Module Boundary
├── View          (Presentation & UI rendering)
├── Controller    (Request orchestration & flow control)
├── Service       (Business rules & domain logic)
├── Model         (Validation & model rules)
└── Repository   (Data-access & SQL persistence)

Shared System Infrastructure
├── Components    (Reusable UI & system utilities)
├── Services      (Cross-module business operations)
└── Repositories  (Shared query & persistence units)
View (Presentation) Renders page markup, subviews, and UI templates. Completely isolated from SQL, database persistence, and business rule evaluation.
Controller (Orchestration) Acts as a thin request orchestrator. Receives user input, triggers Service operations, coordinates validation via Models, and selects View responses. Does not contain raw business logic.
Service (Business Logic) Encapsulates core business processes adhering to Single Responsibility Principles (SRP). Coordinates with other Services and Repositories to execute business flows.
Repository (Data Access) Centralizes SQL queries and data persistence. Isolates database interaction details so data access is traceable, reusable, and decoupled from application logic.
Model (Validation & Schema) Owns validation rules, field definitions, and entity-level integrity checks specific to application records.

Accounts - Reference Modernization

The Accounts module was selected as the reference implementation to prove out the 5-layer architecture. Previously, account management, company hierarchy lookups, and subscription status validations were dispersed across controllers and embedded view scripts.

Through refactoring, Accounts was restructured into explicit View, Controller, Service, Model, and Repository boundaries. Business checks (such as account eligibility and status propagation) were centralized inside dedicated Account Services, while database queries moved to an Account Repository.

This refactoring created a repeatable architectural pattern used as a benchmark for subsequent module updates across the platform.

Explicit Boundaries Account Controller delegates all validation to Account Models and business rules to Account Services.
Traceable Persistence Database queries for account lookups and status updates are isolated in Account Repositories.
Standardized Error Handling Account operations use uniform exception handling and structured validation failure returns.

Engineering Standards & Discipline

The modernization went beyond folder reorganization by instituting rigorous software engineering practices across refactored areas:

SOLID & OOP Principles Applied Single Responsibility (SRP) and Separation of Concerns to keep functions and classes focused, small, and predictable.
Elimination of Magic Values Replaced hardcoded string literals and magic numbers with explicit class constants and enum-like configurations.
Explicit Failure Behavior Eliminated silent failures and unexpected fallback states. Operations fail predictably with clear exception types or structured error outputs.
Consistent Implementation Patterns Standardized naming conventions across classes, methods, variables, and files to make code step-through intuitive for any engineer.

Meters - Domain Logic Case Study

Meters represent consumable credit units associated with client accounts and candidate report generation. Different assessment report types consume varying meter amounts based on report complexity and candidate evaluation scope.

Meter handling is a prime example of why business rules must not live in views or controllers. Generating a report requires multi-step state verification: checking current account meter balances, determining exact report credit costs, validating balance sufficiency, executing report generation, deducting meters atomically upon success, and preventing meter deduction if generation fails.

By housing meter logic inside a dedicated Service and Repository boundary, meter transactions are reliable, atomic, and audit-traceable.

Account / Organization
       ↓
Meter Balance Inspection
       ↓
Select Report Type
       ↓
Determine Required Meter Cost
       ↓
Check Available Balance
       ↓
Sufficient Credits Available?
  ┌────┴──────────────────────────┐
  NO                              YES
  ↓                                ↓
Reject Request Safely     Generate Report File
                              ↓
                        Generation Successful?
                          ┌───────┴────────┐
                          NO              YES
                          ↓                ↓
                    Avoid Meter      Deduct Meter Credits
                    Deduction              ↓
                                     Record Ledger History

Environment & Delivery Feature Controls

To support safe incremental deployment in a live production environment, environment- and feature-level delivery controls were implemented.

Granularity levels include:

  • Global / Environment Level: Toggles system-wide capability modes or environment configurations.
  • Module Level: Controls entire module entry points or refactored subsystem paths.
  • Individual Feature Level: Enables or disables specific new features or experimental code paths.

Introduced environment- and feature-level controls to encapsulate incomplete functionality, module behavior, and selected implementation paths.

This mechanism allows refactored 5-layer components to be deployed to production servers without prematurely exposing unfinished user interfaces or breaking legacy fallback paths.

Structured Exception & Error Handling

Legacy areas occasionally allowed raw framework exceptions or database driver errors to bubble up to the rendering layer, creating poor user experiences and revealing internal system implementation details.

The modernization introduced structured exception handling across controllers and services. System errors are intercepted, categorized, logged with contextual diagnostics, and translated into clean user-facing error notifications.

Key Error Handling Outcome

Reduced direct exposure of raw runtime errors to users, improving security posture and presenting consistent, predictable failure messaging.

Testing Strategy & Verification Distinction

Quality assurance on GoPick uses a clear functional division between official team validation process and additional personal developer verification:

Team / Company Validation
  • Manual functional testing across staging environments.
  • QA team verification against functional spec boundaries.
  • End-to-end user workflow sanity checks prior to release.
Additional Developer Validation
  • PHPUnit unit and component tests written for isolated service/repository logic.
  • Playwright scripts for local end-to-end verification of complex UI flows.
  • PHPStan static analysis checks for type safety and dead-code detection in refactored areas.

Moving behavior into more explicit layers made individual application responsibilities easier to validate independently.

JFP – GoPick Connected Ecosystem

JFP – GoPick operates as a separate specialized assessment application connected to the broader GoPick ecosystem and sharing selected platform data.

It provides dedicated assessment processing capabilities while maintaining data sync boundaries with main GoPick services.

Engineering Documentation & GoPick Manual

A major contribution to project reliability and team continuity was authoring and maintaining the GoPick Manual. Rather than leaving architectural knowledge in developer notes, the manual serves as comprehensive engineering and operational documentation.

Documented areas include:

  • User workflow instructions and operational procedures.
  • System prerequisites, warnings, and implementation guardrails.
  • Developer coding standards and 5-layer architectural rules.
  • Documented current system behavior and QA testing boundaries.
  • Expected error behaviors and maintenance procedures.
Handoff & Onboarding Provides clear structural guidance for onboarding new developers into the codebase.
QA & Dev Alignment Establishes unambiguous system boundary descriptions, reducing discrepancies during QA testing.

Infrastructure & Technology Stack

Framework / App
Yii2 (PHP 8+)
Database
MySQL
In-Memory / Cache
Redis
Hosting / Servers
AWS-hosted application/server environment
Object Storage
AWS object storage / bucket
CDN / Delivery
Amazon CloudFront

The application runs on a high-availability AWS server environment leveraging CloudFront for static asset distribution and secure AWS bucket storage for assessment reports and generated artifacts.

Key Technical Decisions

1. Incremental Modernization Over Full Rewrite

Context: Production platform required active feature delivery while suffering from coupled legacy regions.

Decision: Refactor touch points opportunistically during active development and enforce 5-layer rules on new code.

2. Modular 5-Layer Structure (View, Controller, Service, Model, Repository)

Context: Business logic, SQL queries, and UI presentation were frequently mixed inside single files.

Decision: Institute strict separation where Controllers orchestrate, Services execute business logic, Repositories manage database persistence, and Views render UI.

3. Explicit Repository Boundary for Database Access

Context: SQL queries were scattered throughout controllers and view scripts, making schema changes risky.

Decision: Encapsulate database access inside explicit Repository classes, making data persistence traceable and queries reusable.

4. Dedicated Service Layer for Domain Logic

Context: Complex rules like Meter credit calculation were duplicated across multiple entry points.

Decision: Centralize multi-step domain logic inside SRP-oriented Service classes.

5. Environment & Feature Delivery Controls

Context: Deploying incomplete features or refactored paths risked exposing incomplete UI or breaking legacy fallbacks.

Decision: Implement environment-, feature-, and module-level configuration toggles to encapsulate new execution paths safely.

Engineering Tradeoffs

Incremental Refactoring vs. Clean-Slate Rewrite

Benefit: Significantly lowers operational migration risk and allows revenue-generating feature development to continue uninterrupted.

Tradeoff: Legacy and modernized architectural patterns coexist temporarily during the transition phase.

5-Layer Structure vs. Single-File Controller Logic

Benefit: Superior traceability, isolated testing, clear responsibility ownership, and reduced side-effect risks.

Tradeoff: Requires creating and maintaining more individual class files and boundary contracts per module.

Selective Shared Abstractions vs. Universal Sharing

Benefit: Reduces code duplication without creating brittle global coupling.

Tradeoff: Requires disciplined developer evaluation to distinguish genuine duplication from superficial similarity.

Verified Qualitative Outcomes

Repeatable Architectural Pattern Established a clear, repeatable 5-layer structure for new feature development and refactoring efforts.
Clear Responsibility Ownership Made responsibility location intuitive, reducing time spent searching for business logic or persistence rules.
Enhanced Query & Logic Traceability Database queries and business operations are centralized and easily traceable across execution paths.
Reduced Unintended Cross-Page Side Effects Encapsulating logic into isolated layers prevented changes in one module from breaking unrelated pages.
Safer Feature Delivery Allowed incomplete or modernized features to live safely in production behind environment and feature controls.
Improved Team & QA Alignment Centralized system behavior, developer expectations, and QA boundaries through the GoPick Manual.

Lessons & Engineering Retrospective

What Worked Well?

The incremental migration strategy proved extremely effective. By refactoring Touch Points during active feature development rather than demanding a full rewrite, we delivered continuous value while steadily reducing architectural technical debt.

Key Engineering Takeaway

Explicit boundaries matter more than framework choice. Even within a mature framework like Yii2, establishing clear Service and Repository layers provides tremendous stability, testability, and peace of mind when modifying critical enterprise systems.