Soroush Neyestani

Application Developer

Software Developer

Data Scientist

Project Manager

Senior Project Manager

IT Administrator

Fullstack Developer

Android Developer

iOS Developer

ISP/CRM Modernization

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 Assertions


Project 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

That architecture was influenced by the technical and operational environment of the time.

The software needed to be deployed quickly, remain lightweight and operate inside an infrastructure where external access was heavily restricted at the network and firewall level.

For 2014, the system was practical.

Twelve years later, however, my definition of a successful software system had changed significantly.

The question was no longer simply:

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

In some cases, several of these concerns existed within the same PHP file.

This reduced abstraction overhead and helped keep the application compact.

It also allowed features to be implemented quickly.

But as the system grew, the same characteristics created long-term architectural limitations.

Large files became responsible for multiple business concerns.

Database operations were closely connected to application behavior.

Business rules were sometimes implicit in execution flow rather than explicitly modeled.

Authorization behavior was distributed across different parts of the system.

Testing individual rules independently was difficult.

The architecture worked, but much of the system's knowledge existed inside the implementation itself.


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

Only after understanding those concepts could the system be reconstructed in a modern architecture.


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

Instead of allowing these concepts to exist implicitly across unrelated files, they are modeled as identifiable parts of the system.

This makes boundaries clearer and allows individual business rules to be tested independently.


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

The purpose is to make system behavior explicit.

Instead of relying on statements such as:

"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

The objective is to prevent unrelated business responsibilities from accumulating inside a single customer record or controller.

Customer identity and customer-related operations therefore become explicit parts of the domain model.


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

A major architectural improvement was to make these transitions explicit.

Instead of directly modifying status fields from arbitrary parts of the application, transitions can be modeled as business operations.

This creates an important distinction:

A state describes where an entity currently is.

A transition describes how and why it moved there.

That difference improves auditability and makes invalid transitions easier to prevent.


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

A particularly important principle is the use of an:

Immutable Financial Ledger

Once a financial event has been committed to the ledger, normal application operations should not silently rewrite its history.

Corrections should be represented through explicit compensating or subsequent operations rather than destructive modification of historical financial records.

This improves:

  • traceability
  • auditability
  • reproducibility
  • financial consistency

It also creates strong invariants that can be tested automatically.


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

Idempotency is therefore tested as behavior rather than documented merely as an implementation recommendation.


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

This allows authorization to be expressed explicitly rather than scattered across controllers and views.

The test suite also verifies authorization boundaries.

It is not sufficient that an authorized user can perform an operation.

The system must also demonstrate that an unauthorized user cannot perform it.


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?

The modernized platform therefore includes explicit audit behavior.

At the same time, auditability must not become a mechanism for leaking sensitive information.

The audit system is designed so that security-sensitive values do not become permanently stored merely because an operation was logged.

This behavior is also covered by automated tests.


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

The modern architecture therefore treats import and reconciliation as defined workflows rather than arbitrary database writes.

Repeated processing must not silently create duplicated business state.

Validation and idempotency principles are applied where appropriate to make import behavior predictable and reproducible.


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

These integrations are treated as external boundaries rather than allowing provider-specific behavior to spread throughout unrelated business logic.

This separation makes communication workflows easier to replace, test and reason about.


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

The project includes an OpenAPI specification so that important parts of the external interface can be described in a machine-readable format.

This reduces ambiguity between implementation and documentation.


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

The objective is therefore not to maximize the number of assertions.

The objective is to make important system behavior machine-verifiable.


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

This is an important distinction.

The tests do not merely ask:

"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:

  1. creates a payment,
  2. updates an invoice,
  3. writes a ledger entry,
  4. sends an event,
  5. then fails.

If the transaction boundaries are incorrect, the application may report failure while leaving part of the operation committed.

The modern reference therefore tests not only successful outcomes but also failure behavior.

A rejected or failed operation must not leave the system in an unintended intermediate state.

This principle is critical for financial and operational consistency.


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

Application-level validation and database-level integrity complement each other.

Validation provides meaningful application behavior.

Database constraints provide a final structural boundary against invalid persistent state.


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

If the same inputs unexpectedly produce different outputs after a change, the difference can be investigated systematically.


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

Code explains how something currently works.

Architecture documentation should also explain why important boundaries and constraints exist.

That distinction becomes increasingly important as systems grow.


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

The modernization process therefore follows a different sequence:

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:

  1. What does the legacy system currently do?
  2. Why does that behavior exist?
  3. What should the modern specification define?

Only after answering those questions can behavior be safely preserved, corrected or intentionally removed.

That is why I consider this project an exercise in software engineering rather than simply framework migration.


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

A system should not depend entirely on the original developer remembering why a particular condition exists.

Important knowledge should be represented in architecture, domain rules, contracts and tests.

That is perhaps the biggest difference between the two versions of this project.


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

With 836 passing automated tests and 7,274 passing assertions, critical behavior is no longer dependent solely on manual testing or undocumented assumptions.

The system's rules can be executed.

Its invariants can be challenged.

Its security boundaries can be tested.

Its financial behavior can be verified.

And architectural changes can be evaluated against a known behavioral baseline.

That is the fundamental difference between the project I built in 2014 and the system I re-engineered in 2026.


From Working Software to Verifiable Software

The original system proved that the business processes could be automated.

The modern reference goes further.

It defines what those processes mean, which transitions are valid, who may execute them, which side effects are permitted, and which invariants must always remain true.

2014 → 2026

From a compact legacy ISP/CRM application to an explicit, testable and verifiable software architecture.