Skip to main content

Command Palette

Search for a command to run...

When I Moved from End-User to IT Consulting Development

Lessons about legacy systems, refactoring strategies, and the mindset shift between product teams and consulting engineers.

Updated
5 min read
When I Moved from End-User to IT Consulting Development
D

I'm a highly motivated and experienced developer expertise in leveraging the power of .NET Core Technology. Currently collaborating with an Australian company based in Nusa Dua, Bali, Indonesia, to deliver innovative application development services that push the boundaries of what technology can achieve, and also contribute to the ever-evolving landscape of the global IT industry

There was a moment in my career when my perspective as a software developer changed drastically.

Before joining the consulting world, I worked in an end-user environment. My mindset back then was simple:

If I want to understand a system, I need to understand it from A to Z.

I believed the only responsible way to work in a large system was to understand every service, every helper, and every part of the codebase before making changes.

But when I stepped into the IT consulting world, that mindset changed very quickly.

And honestly, it hit me hard at first.


The First Shock: Legacy Systems in the Real World

One of the first things I encountered was a legacy codebase that looked something like this:

ApplicationController.cs
≈ 150,000 lines
≈ touched by ~15 developers
≈ years of accumulated fixes

Inside that file you could find almost everything:

  • business logic

  • database queries

  • document processing

  • notification logic

  • validation

  • integration with external services

It was what engineers often call a God Controller 😇.

And like many legacy systems, you could see its history directly in the code.

Sometimes comments looked like this:

// TEMP FIX
// BUG-20412 quick fix
// workaround for client X

Every line represented a moment in time when something happened in production and someone had to fix it quickly.

At that moment I realized something important.

This wasn’t just messy code.

It was years of accumulated decisions.


The Rewrite Instinct

When developers see code like this, the first instinct is usually the same:

“We should rewrite this system.” 😎

But legacy systems hide a lot of complexity.

Inside those thousands of lines you often find things like:

  • undocumented business rules

  • special handling for specific customers

  • bug workarounds discovered in production

  • edge cases nobody remembers anymore

  • Some tribal knowledge

Rewriting everything from scratch might create cleaner code, but it can also remove behavior that users unknowingly depend on.

That’s why experienced engineers often say:

Legacy systems are not just code. They are history.


The Consulting Mindset Shift

Working in consulting environments forced me to change how I approach large systems.

Instead of asking:

How do I understand everything?

The better question became:

What problem do I need to solve right now?

That shift changed everything.

Instead of reading the entire codebase, I started focusing on execution flow.

For example:

Request
   ↓
Controller / Function
   ↓
Service Layer
   ↓
Database

Or in message-driven systems:

Queue Message
   ↓
Consumer / Function
   ↓
Service
   ↓
Database

Once you understand the flow, you can ignore most of the system and focus only on the relevant path.


Living Between Two Worlds

Many systems today exist in two architectural worlds at the same time.

Legacy System

Monolithic Application
   ├── Large Controllers
   ├── Mixed Responsibilities
   └── Years of Business Logic

Modern System

Modern Services (.NET 8)
   ├── Clean Architecture
   ├── Dependency Injection
   └── Smaller Focused Services

During migration, these two systems usually run side by side.


How Migration Actually Happens

Instead of rewriting everything, many companies adopt a gradual migration strategy.

The architecture evolution often looks like this.

Phase 1 — Pure Legacy

Client
   ↓
Legacy Monolith
   ├── ApplicationController
   ├── Document Logic
   ├── Notification Logic
   └── Activity Tracking

Phase 2 — First Service Extracted

Client
   ↓
Legacy Monolith
   ├── ApplicationController
   ├── Document Logic
   └── Activity Tracking

Notification Service (.NET 8)

The new system handles only one responsibility.


Phase 3 — Hybrid Architecture

Client
   ↓
API Gateway
   │
   ├── Legacy Monolith
   │      ├── ApplicationController
   │      └── Remaining Legacy Logic
   │
   ├── Notification Service
   ├── Document Service
   └── Activity Service

Now responsibilities are gradually moved to modern services.


Phase 4 — Legacy Shrinks

Client
   ↓
API Gateway
   │
   ├── Modern Services
   │      ├── Notification
   │      ├── Document
   │      ├── Activity
   │      └── Workflow
   │
   └── Legacy System (minimal)

Eventually the legacy system becomes smaller and easier to retire.

This strategy is often called the Strangler Pattern.


What I Learned From This Experience

The biggest lesson for me was this:

Building clean systems is important.

But understanding messy systems is also a valuable engineering skill.

Modern engineering teaches you how to design systems.

Legacy engineering teaches you how to navigate complexity without getting lost.

Both skills matter in real-world software development.


Final Thoughts

Many developers dream about working only with clean, modern codebases.

But real systems evolve through years of deadlines, quick fixes, and changing business requirements.

Legacy systems may look chaotic at first.

But if you look closely, they tell the story of how software adapts to real-world problems.

And sometimes the best engineering decision is not to destroy the old system.

Sometimes the best decision is simply to grow beyond it step by step. 🧑‍💻