ISP/CRM Modernization
Re-engineering a production ISP management platform from a legacy PHP system into a testable, domain-oriented reference architecture Original system: 2014 Re-engineered: 2026 Category: Enterprise Software / Legacy Modernization / Backend Engineering Reference implementation: Laravel 12 Database: PostgreSQL Architecture: Modular Monolith / Domain-Oriented Architecture Testing: 836 Automated Tests / 7,274 AssertionsProject Overview
In 2014, I developed one of my major software projects for an Internet Service Provider. The system was not a simple website or administrative dashboard. It was an operational ISP/CRM platform responsible for managing a significant part of the company’s day-to-day business processes, including customers, services, contracts, billing, payments, support, communications and operational workflows. At the time, the application was built primarily with traditional PHP. The complete system was remarkably compact:- approximately 23 MB
- around 104 core PHP files
- individual files containing up to approximately 3,000 lines of code
Does the software work?The new questions were:
Can its behavior be precisely defined? Can its business rules be independently verified? Can financial operations be protected by explicit invariants? Can authorization decisions be tested rather than assumed? Can the architecture evolve without silently changing existing behavior? Can another engineer understand why the system behaves the way it does?Those questions became the foundation of the 2026 modernization.
The Original 2014 System
The original application followed a development style that was common and practical for many PHP applications of that period. Responsibilities that would normally be separated in a modern architecture often existed close together. A single application flow could involve:- SQL queries
- business rules
- authentication
- authorization
- validation
- HTML rendering
- form processing
- session management
- financial calculations
- customer operations
- service operations
Why I Revisited the Project
When I returned to the project in 2026, I deliberately avoided treating the modernization as a framework upgrade. Replacing old PHP syntax with modern PHP would not solve the architectural problem. Moving existing code into a newer framework without understanding its behavior would simply move technical debt into a newer directory structure. So I approached the project differently. The modernization started with: reverse engineering the behavior of the legacy system. The objective was to identify:- business entities
- business rules
- workflows
- state transitions
- authorization requirements
- financial invariants
- side effects
- integrations
- data relationships
- historical behavior
From Legacy Code to Domain Model
The legacy application contained years of operational knowledge. That knowledge needed to be extracted from implementation details and converted into explicit domain concepts. The modernized system therefore separates major business areas such as:- CRM
- Customer Management
- Customer Contacts
- Contracts
- Service Plans
- Service Lifecycle
- Service Lines
- Service Cases
- Service Case Transitions
- Invoices
- Payments
- Financial Ledger
- Support
- Messaging
- SMS
- Telegram Integration
- Import & Reconciliation
- Authentication
- Authorization
- RBAC
- Audit Logging
- Customer Portal
- Idempotency
- API Contracts
Architecture
The 2026 reference implementation is based on Laravel 12 and PostgreSQL. However, Laravel is not being used merely as a modern replacement for the original PHP application. The reference implementation has a more important role:It acts as an executable specification of the system’s expected behavior.This means the implementation does not only contain working features. It also defines, through code, constraints and automated tests, what the system is allowed and not allowed to do. The architecture follows a modular, domain-oriented approach. Instead of organizing the entire system primarily around controllers and database tables, business capabilities are treated as architectural concepts with explicit responsibilities. The result is a system that is easier to reason about, test and maintain.
The Reference Implementation
One of the most important milestones of the modernization was reaching a frozen Laravel reference state. “Frozen” in this context means that the implementation has reached a defined behavioral baseline. The reference contains:- database schema
- domain rules
- authorization behavior
- API contracts
- validation rules
- expected state transitions
- test scenarios
- reference datasets
- architectural documentation
“This is how the old system probably worked.”the project can express:
“This is the behavior that the reference implementation defines and verifies.”That distinction is fundamental to the modernization strategy.
Customer and CRM Domain
Customer management is one of the central domains of the platform. A customer is not represented merely as a row containing a name and telephone number. The model supports relationships between concepts such as:- customers
- contacts
- services
- service lines
- credentials
- contracts
- invoices
- payments
- support cases
- communications
Service Lifecycle
Internet services naturally have a lifecycle. A service can move through different operational states over time. These transitions may affect:- activation
- suspension
- cancellation
- credentials
- billing
- technical provisioning
- customer communication
Financial Architecture
Financial behavior is one of the areas where implicit application logic creates the highest risk. The modernized architecture therefore treats financial operations as explicit domain behavior. The system models concepts including:- invoices
- payments
- financial entries
- reconciliation
- financial state transitions
- traceability
- auditability
- reproducibility
- financial consistency
Idempotency
Idempotency became another explicit architectural concern. Consider a payment request. A client sends the request. The server processes the payment successfully. Before the response reaches the client, the network connection fails. The client retries. Without idempotency protection, the second request could create another financial transaction. The modernized system therefore treats retry safety as part of the business contract. Equivalent requests using the same idempotency context must not create duplicate financial side effects. This is especially important for:- payments
- imports
- external integrations
- asynchronous operations
- retryable API requests
Authentication and Authorization
Authentication answers:Who is the user?Authorization answers:
What is this user allowed to do?The modern architecture treats these as separate concerns. A Role-Based Access Control model was introduced with concepts such as:
- roles
- permissions
- role-permission relationships
- user-role relationships
- user-specific permission overrides
Tenant and Customer Data Isolation
Authorization is particularly important when customer-specific data is involved. An authenticated user should not automatically gain access to unrelated customer information. The architecture therefore treats object-level authorization and data isolation as explicit security requirements. Tests verify scenarios where users attempt to access resources outside their permitted scope. This helps prevent one of the most dangerous classes of application vulnerabilities: valid authentication combined with invalid resource access.Audit Logging
Enterprise systems need more than application logs. They need to answer questions such as:- Who performed an operation?
- What operation was performed?
- Which entity was affected?
- When did it happen?
- What was the result?
Import and Reconciliation
Legacy enterprise systems frequently exchange information with external systems through files, imports or periodic synchronization processes. Imports create several risks:- duplicate records
- partially processed batches
- inconsistent retries
- invalid relationships
- conflicting data
Messaging and External Communication
The original platform also interacted with external communication channels. The modern domain model therefore includes communication capabilities such as:- SMS
- Telegram
- internal messaging
API-First Behavioral Contracts
The reference implementation exposes defined API behavior. An API is not treated merely as a collection of routes. Each operation has a contract describing aspects such as:- request structure
- validation
- authentication
- authorization
- response structure
- failure behavior
- side effects
Automated Testing
One of the largest differences between the 2014 system and the 2026 reference implementation is the role of automated verification. Current reference status: 836 Automated Tests — PASS Within those tests: 7,274 Assertions — PASS These numbers represent two different concepts. A test represents a scenario or unit of expected behavior. An assertion verifies a specific condition within that scenario. For example, a test may execute a payment operation. Inside that scenario, multiple assertions may verify that:- the response status is correct
- the payment exists
- the amount is correct
- exactly one ledger entry was created
- the invoice state changed correctly
- an audit event was generated
- unrelated records were not modified
What the Tests Actually Protect
The test suite covers behavior across different architectural layers. Examples include verifying that:- unauthorized users cannot execute protected operations
- users cannot access customer data outside their authorization scope
- issued invoices cannot be silently modified
- financial history cannot be destructively rewritten
- duplicate payment requests do not generate duplicate financial effects
- idempotency rules remain effective
- audit logs do not persist sensitive information
- repeated imports do not create unintended duplicates
- invalid state transitions are rejected
- database invariants remain valid
- failed operations do not produce unintended side effects
“Does this endpoint return HTTP 200?”They ask:
“Did the complete business operation behave exactly as expected, including what must not happen?”Negative behavior is part of the specification.
Side-Effect Control
One of the most important lessons in enterprise software is that a failed operation can be more dangerous than an operation that simply throws an error. Imagine an operation that:- creates a payment,
- updates an invoice,
- writes a ledger entry,
- sends an event,
- then fails.
Database Architecture
The reference implementation uses PostgreSQL. The database schema is not treated as passive storage. It participates in protecting important invariants through:- relational constraints
- foreign keys
- uniqueness rules
- appropriate data types
- explicit relationships
Golden Dataset
A defined Golden Dataset is part of the reference architecture. The purpose of a golden dataset is to provide deterministic input data with known expected behavior. Instead of validating complex system behavior against arbitrary development records, the project can execute scenarios against a controlled reference dataset. This improves:- reproducibility
- regression detection
- debugging
- behavioral comparison
Golden Master Testing
The project also uses the concept of Golden Master Tests. Golden master testing is particularly valuable when modernizing legacy systems. A legacy system may contain behavior that is difficult to describe completely from documentation alone. By capturing known expected outputs and comparing future execution against them, behavior can be protected during architectural changes. This does not mean that every historical behavior must be preserved forever. It means behavioral changes become deliberate. A difference should exist because an engineering decision changed the specification — not because a refactoring silently altered behavior.Architecture Package
The project contains an architecture package describing the system beyond implementation details. The goal is to preserve engineering knowledge about:- domain boundaries
- important invariants
- system responsibilities
- data relationships
- API behavior
- security expectations
- integration boundaries
Security as System Behavior
Security is not treated as one middleware layer placed in front of the application. It exists across multiple levels: Authentication Who is making the request? Authorization Is the actor allowed to perform this operation? Object-level access Is the actor allowed to access this specific resource? Data integrity Can invalid state be persisted? Financial integrity Can financial history be silently changed? Audit safety Can sensitive information leak into permanent logs? Replay protection Can retrying a request create duplicate effects? These properties are part of the architecture and, where practical, part of the automated test suite.Legacy Modernization Philosophy
One of the most important decisions in this project was not to equate modernization with rewriting. A rewrite that ignores the accumulated business behavior of the previous system can easily produce cleaner code and a worse product. Legacy systems often contain years of implicit knowledge:- exceptions
- operational workflows
- business constraints
- edge cases
- historical assumptions
Observe → Understand → Model → Specify → Implement → Verify
rather than:
Delete → Rewrite
This approach significantly reduces the risk of losing important business behavior during modernization.
2014 vs. 2026
The evolution of the project can be summarized clearly.2014
The primary engineering objective was:Build a compact operational system that solves the company’s immediate business requirements.The architecture optimized strongly for practical delivery and a small deployment footprint.
2026
The engineering objective became:Build an explicit behavioral reference in which domain rules, security boundaries, financial invariants, APIs and expected side effects can be independently verified.The difference is therefore much larger than:
Old PHP → Laravel 12
The real transformation is:
Implicit behavior → Explicit domain rules
Large procedural flows → Defined responsibilities
Database operations → Protected invariants
Manual confidence → Automated verification
Scattered authorization → Explicit RBAC
Mutable financial history → Immutable ledger principles
Retry uncertainty → Idempotent operations
Implementation knowledge → Executable specification
Key Metrics
| Metric | Value |
|---|---|
| Original implementation | 2014 |
| Re-engineered reference | 2026 |
| Original application size | ~23 MB |
| Legacy core PHP files | ~104 |
| Largest legacy files | Up to ~3,000 lines |
| Reference framework | Laravel 12 |
| Reference database | PostgreSQL |
| Automated tests | 836 PASS |
| Assertions | 7,274 PASS |
| API specification | OpenAPI |
| Architecture approach | Modular / Domain-Oriented |
Technical Highlights
- Legacy system reverse engineering
- Domain decomposition
- Modular monolith architecture
- Laravel 12 reference implementation
- PostgreSQL relational model
- Explicit service lifecycle
- Contract and billing domains
- Invoice and payment modeling
- Immutable financial ledger principles
- Idempotent operations
- Role-Based Access Control
- User-specific permission overrides
- Object-level authorization
- Audit logging
- Sensitive-data protection
- Import and reconciliation workflows
- SMS and Telegram integration boundaries
- API contracts
- OpenAPI specification
- Golden Dataset
- Golden Master Tests
- Automated regression testing
- Database invariant validation
- Side-effect verification
- Architecture documentation
Engineering Challenges
The difficult part of this modernization was not converting PHP syntax. The difficult part was recovering the meaning behind twelve years of legacy behavior. A line of old code may look unnecessary until the historical workflow that required it is understood. A database field may look redundant until another process depending on it is discovered. An unusual conditional branch may represent an important business exception rather than poor programming. For this reason, modernization required separating three questions:- What does the legacy system currently do?
- Why does that behavior exist?
- What should the modern specification define?
What Changed in My Engineering Approach
The project also provides a useful comparison between two stages of my own development as a software engineer. In 2014, success was primarily measured by whether the application solved the operational problem reliably enough to be used. That remains necessary. But it is no longer sufficient. Today I also expect important behavior to be:- explicit
- isolated
- testable
- reproducible
- documented
- observable
- secure
- maintainable
Result
The result of the modernization is not simply a cleaner version of a 2014 PHP application. It is a behavioral reference for a complex ISP/CRM domain. The Laravel 12 implementation defines the expected behavior of the platform through:- executable business rules
- relational constraints
- authorization policies
- API contracts
- deterministic reference data
- automated tests
- architectural documentation