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.
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
- Dashboard
- Account
- Candidate
- Assessment
- Test Norms
- Meters
- Reports
- users
- Role/Permission
- Activity log
- assessment account schedule
- email template
- emails
- advisory
- help desk
- test settings
- report templates
- 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:
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.
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:
- 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.
- Strict Greenfield Standard: All newly added modules and features must strictly adhere to the modular 5-layer structure from day one.
- Leave Untouched Legacy Stable: Untouched legacy regions remain operational until future work touches them, minimizing unnecessary regression risk.
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)
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.
Engineering Standards & Discipline
The modernization went beyond folder reorganization by instituting rigorous software engineering practices across refactored areas:
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:
- Manual functional testing across staging environments.
- QA team verification against functional spec boundaries.
- End-to-end user workflow sanity checks prior to release.
- 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.
Infrastructure & Technology Stack
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
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.