Over 10 years we help companies reach their financial and branding goals. Engitech is a values-driven technology agency dedicated.

Gallery

Contacts

411 University St, Seattle, USA

+1 -800-456-478-23

Why We Architect Software: The Reason Principles Matter More Than Clever Code

Imagine a small software project.

One developer.

One customer.

One database.

A few screens.

The first version is easy.

You create a controller.

Write some business logic.

Save data.

Return a response.

Everything works.

Then the customer asks for one more feature.

Then another.

Then another.

Six months later, the project has:

50 controllers
30 services
hundreds of database queries
background jobs
authentication
notifications
reports
integrations
multiple developers

Now something strange begins to happen.

A tiny change in one place breaks another.

A new developer needs weeks to understand the code.

Business rules are duplicated.

Database logic appears everywhere.

Controllers become enormous.

Services depend on other services that depend on even more services.

Nobody is completely sure what can safely be changed.

The software still works.

But the team is becoming afraid of it.

And that is the moment architecture starts to matter.

Not because software architects enjoy drawing boxes and arrows.

Not because design patterns look sophisticated.

Not because principles such as SOLID sound impressive.

We architect software because software changes.

And badly structured software becomes increasingly expensive every time it changes.


The First Version Is Almost Never the Problem

Most software is easy to write once.

The difficult part is writing version two.

And version ten.

And version one hundred.

Imagine an invoicing feature.

Version one:

Create invoice
Calculate total
Save invoice

Simple.

Then the business changes.

Add VAT.

Then discounts.

Then multiple currencies.

Then approval workflow.

Then partial payment.

Then credit notes.

Then electronic invoicing.

Then integration with accounting.

Then region-specific tax rules.

Then audit requirements.

The original feature does not disappear.

It evolves.

That is the reality of enterprise software.

The code you write today becomes the foundation someone must modify tomorrow.

Architecture exists largely to make that future change survivable.


What Is Software Architecture Really?

People often imagine architecture as:

API
 ↓
Service
 ↓
Repository
 ↓
Database

That is structure.

But architecture is deeper than layers.

Architecture is about deciding:

Which parts of the system are allowed to know about which other parts?

That question determines how change spreads.

Suppose business logic directly depends on:

SQL Server
Stripe
AWS S3
SendGrid
Redis
HTTP

Then replacing any of those technologies may require changing business logic.

Now imagine instead:

Business Rules
     ↓
Interfaces
     ↓
Infrastructure Implementations

The business logic says:

StoreFile()
SendNotification()
ProcessPayment()

Infrastructure decides whether those operations use:

S3
Azure Blob
SendGrid
Stripe

Now technology can change without rewriting the core business rules.

That is architecture.

It is the management of dependencies.


Why Dependencies Matter

Imagine a spider web.

Pull one thread.

Several others move.

Software behaves similarly.

Every dependency creates a relationship.

Some relationships are necessary.

But too many uncontrolled relationships create fragility.

Suppose:

InvoiceController

directly uses:

DbContext
EmailSender
PaymentGateway
PdfGenerator
Redis
FileStorage

Now the controller knows almost everything.

Changing payment logic affects the controller.

Changing storage affects the controller.

Testing the controller requires constructing the whole world.

The problem is not that these technologies are bad.

The problem is that too many decisions have been concentrated in one place.

Architecture tries to separate those decisions.


Architecture Is About Containing Change

Imagine a ship.

A well-designed ship contains water when one compartment is damaged.

It does not allow a small leak to flood everything immediately.

Good software architecture works similarly.

A change in:

Email Provider

should mostly remain inside the notification infrastructure.

A change in:

Database Provider

should mostly remain inside persistence.

A change in:

Tax Calculation

should mostly remain inside business rules.

When unrelated changes spread throughout the system, the architecture is leaking.

This leads to one of the best ways to judge architecture:

How far does a change travel?

If adding one business rule requires editing twenty unrelated files, something is wrong.


Then Why Do We Need Principles?

Because architecture is full of decisions.

And humans are inconsistent.

Without principles, developers often make decisions based on convenience.

Today:

I'll just put this logic here.

Tomorrow:

I'll just copy this function.

Next week:

I'll just call the database directly.

Each shortcut seems harmless.

Together, they become architecture.

Principles act like guardrails.

They do not tell you exactly what code to write.

They help prevent certain classes of mistakes.

SOLID is one of the best-known collections of these principles.


S — Single Responsibility Principle

Consider this class:

InvoiceService

It:

Creates invoices
Calculates taxes
Generates PDFs
Sends emails
Writes audit logs
Stores files
Processes payments

It works.

But it has many reasons to change.

Change tax policy?

Modify InvoiceService.

Change PDF layout?

Modify InvoiceService.

Change email provider?

Modify InvoiceService.

Change payment gateway?

Again, modify InvoiceService.

One class becomes the center of unrelated changes.

The Single Responsibility Principle asks us to separate responsibilities that change for different reasons.

Maybe:

Invoice
TaxCalculator
InvoicePdfGenerator
NotificationService
PaymentProcessor

Now each component has a clearer purpose.

SRP is not about making every class tiny.

It is about preventing unrelated responsibilities from becoming permanently entangled.


O — Open/Closed Principle

Suppose we support two payment methods:

Cash
Card

The code looks like:

if (type == PaymentType.Cash)
{
    // ...
}
else if (type == PaymentType.Card)
{
    // ...
}

Then the business adds:

Bank Transfer
Mobile Banking
Wallet

The conditional grows.

Every new payment type requires modifying existing logic.

The Open/Closed Principle encourages designs that are:

open for extension, closed for unnecessary modification.

Instead of repeatedly changing the caller, perhaps define:

public interface IPaymentProcessor
{
    Task ProcessAsync(Payment payment);
}

Then:

CashPaymentProcessor
CardPaymentProcessor
BankTransferPaymentProcessor

Adding another implementation does not require rewriting the core workflow.

This principle matters most in places where variation is expected.

You do not need interfaces everywhere.

You need extensibility where change actually happens.


L — Liskov Substitution Principle

This principle sounds academic until you see it violated.

Suppose:

Bird

has:

Fly()

Then:

Eagle : Bird
Penguin : Bird

Now someone calls:

penguin.Fly()

We have modeled the world badly.

The inheritance relationship claims:

Every Bird can Fly

which is false.

Liskov Substitution Principle roughly asks:

Can a subtype safely be used wherever the parent type is expected?

If not, the abstraction is lying.

This applies constantly in real software.

If an implementation throws:

NotSupportedException

for half the interface methods, perhaps it does not really belong to that abstraction.

Good architecture depends on truthful contracts.


I — Interface Segregation Principle

Imagine this interface:

public interface IWorker
{
    void Work();
    void Eat();
    void Sleep();
}

Now we create:

HumanWorker
RobotWorker

The robot can work.

But eating and sleeping make no sense.

The interface is too broad.

Interface Segregation says clients should not depend on operations they do not need.

Instead:

IWorkable
IFeedable
IRestable

In enterprise software, large interfaces create unnecessary coupling.

A report service may need only:

ReadInvoices()

but if it depends on:

IInvoiceRepository

containing twenty write operations, it is coupled to capabilities it never uses.

Smaller focused contracts improve clarity.


D — Dependency Inversion Principle

This one sits very close to architecture.

Suppose your business logic directly creates:

var sender = new SmtpEmailSender();

Now the business rule depends on SMTP.

What if tomorrow you use:

SendGrid
Amazon SES
Azure Communication Services

The business logic must change.

Dependency Inversion says high-level policy should not depend directly on low-level implementation details.

Instead:

public interface INotificationSender
{
    Task SendAsync(...);
}

The business logic depends on the abstraction.

Infrastructure implements it.

Conceptually:

Business Logic
      ↓
INotificationSender
      ↑
SmtpNotificationSender

Notice the direction.

The infrastructure depends on a contract defined around business needs.

The core application does not depend on SMTP.

This idea becomes central in Clean Architecture.


Enter Clean Architecture

Robert C. Martin's Clean Architecture popularized a powerful architectural idea:

Business rules should live at the center of the system.

Everything else should surround them.

Imagine concentric circles.

At the center:

Entities / Domain Rules

Around them:

Use Cases / Application Rules

Further out:

Adapters
Controllers
Repositories
Presenters

At the outer edge:

Database
Web Framework
UI
External Services

The important rule is not the circles themselves.

It is the dependency rule.

Dependencies should point inward.

The inner business logic should not know whether the application uses:

ASP.NET Core
EF Core
PostgreSQL
MySQL
Redis
RabbitMQ
React
AWS
Azure

Those are implementation details.

Important details, yes.

But still details.


Frameworks Are Tools, Not Your Business

Imagine a company has a rule:

A purchase order over $50,000 requires two approvals.

Where should that rule live?

Inside:

ASP.NET Controller?

No.

Inside:

EF Core DbContext?

Probably not.

Inside:

React component?

Definitely not.

That rule belongs to the business.

The application should be able to express it independently of the framework.

Something like:

PurchaseOrderApprovalPolicy

or within:

PurchaseOrder.Approve(...)

depending on the model.

The important point is that the rule should survive if the framework changes.

ASP.NET Core is not your business.

EF Core is not your business.

PostgreSQL is not your business.

They are tools supporting your business.

Clean Architecture tries to preserve that distinction.


The Database Is a Detail

This statement often surprises developers.

"How can the database be a detail? Everything is stored there!"

The database is extremely important operationally.

But from the perspective of business rules, it is still an implementation mechanism.

Consider:

Customer places order

The business does not fundamentally care whether the order is stored in:

SQL Server
PostgreSQL
MySQL
Document database

It cares that the order can be stored and retrieved reliably.

So the use case might depend on:

IOrderRepository

rather than a specific database API.

Then infrastructure implements the repository using EF Core.

PlaceOrderUseCase
      ↓
IOrderRepository
      ↑
EfCoreOrderRepository

Now the direction of dependency protects the use case.


Why This Matters for Testing

Suppose this business rule:

A customer cannot place an order above their credit limit.

If the rule is buried inside an ASP.NET controller that directly talks to the database and payment gateway, testing it becomes painful.

You may need:

web server
database
test data
network
configuration

just to test one rule.

If the rule exists in a focused use case or domain object, the test might be:

var customer = new Customer(creditLimit: 1000);

var result = customer.CanPlaceOrder(amount: 1500);

Assert.False(result);

Fast.

Simple.

Deterministic.

Architecture makes business rules easier to test because infrastructure is pushed outward.

That is not an accidental benefit.

It is one of the reasons the architecture exists.


Clean Architecture Is Not About Folder Names

A project can contain:

Domain
Application
Infrastructure
WebApi

and still have terrible architecture.

If:

Domain

references EF Core,

and:

Application

directly calls ASP.NET types,

and every layer references every other layer,

then the folder names mean nothing.

Clean Architecture is about dependency direction.

Not directory aesthetics.

You could put everything in one project and still maintain clean boundaries.

Or create twenty projects and still have a tightly coupled mess.

Architecture lives in relationships, not folder names.


Architecture Has a Cost

This is important.

Every abstraction has a cost.

Suppose you are building a tiny internal script.

You probably do not need:

Domain project
Application project
Infrastructure project
Repositories
Use cases
Ports
Adapters
Factories
Mediators

for 300 lines of code.

That would make the architecture more complicated than the problem.

Good architecture is not maximum abstraction.

It is appropriate structure.

A two-week prototype and a ten-year ERP platform should not have identical architecture.

The expected lifetime, team size, complexity, and rate of change all matter.

Principles guide judgment.

They do not replace judgment.


Overengineering Is Architecture's Shadow

Developers learn Clean Architecture and SOLID.

Then excitement takes over.

Every class gets an interface.

Every operation gets a use case.

Every entity gets a repository.

Every method gets a factory.

Then a request travels through:

Controller
 ↓
Mediator
 ↓
Handler
 ↓
UseCase
 ↓
Interactor
 ↓
Service
 ↓
Repository
 ↓
UnitOfWork
 ↓
DbContext

to execute:

SELECT Name FROM Customers

The architecture has technically achieved separation.

But perhaps at the expense of comprehension.

There is a difference between:

decoupling

and:

ceremonial indirection.

Architecture should make important complexity easier to manage.

If it makes simple things incomprehensible, reconsider it.


Why Rules Like SOLID Can Become Dangerous

Principles become harmful when developers treat them as laws.

Suppose someone says:

"Every class must have one method because SRP."

That is not SRP.

Or:

"Every dependency must have an interface because Dependency Inversion."

Not necessarily.

Or:

"Never use switch because Open/Closed Principle."

Again, too simplistic.

Principles describe pressures and tradeoffs.

They help us recognize risks.

Real design requires context.

Sometimes a switch is clearer.

Sometimes a direct dependency is perfectly acceptable.

Sometimes duplication is cheaper than the wrong abstraction.

Good engineering asks:

What problem is this principle helping me avoid here?

If you cannot answer that, perhaps you are applying it mechanically.


Architecture Begins With Change

A useful way to design software is to ask:

What is likely to change independently?

In an e-commerce system:

Payment provider may change.
Shipping provider may change.
Tax rules may change.
Database probably changes less frequently.
Business workflow may evolve constantly.

Those become candidates for boundaries.

Architecture should not treat every part of the system as equally variable.

Stable things and volatile things should not necessarily be coupled together.

You isolate volatility.

That makes future change cheaper.


Business Rules Deserve the Strongest Protection

Imagine spending five years building an ERP system.

The business rules represent enormous knowledge.

How inventory is valued.

How manufacturing costs are calculated.

How approval works.

How payroll is calculated.

How accounting entries are generated.

That knowledge is more valuable than the web framework.

Frameworks can be replaced.

Business understanding is much harder to reconstruct.

Architecture should therefore protect the business rules from technical churn.

Today:

ASP.NET Core

Tomorrow perhaps another framework.

Today:

MySQL

Tomorrow PostgreSQL.

Today:

RabbitMQ

Tomorrow something else.

But:

How this company calculates production cost

may remain for decades.

That knowledge belongs near the center.


Think of Architecture Like a City

A city without planning can grow.

Houses appear.

Roads appear.

Shops appear.

For a while, everything works.

Then population grows.

Traffic becomes terrible.

Sewage systems cannot handle demand.

Industrial buildings sit beside schools.

Expanding one road requires demolishing half the neighborhood.

Software grows the same way.

Early shortcuts become permanent infrastructure.

Architecture is city planning for code.

You decide:

Where are the boundaries?

How do things communicate?

Where can expansion happen?

Which areas should remain isolated?

What infrastructure is shared?

You cannot predict every future building.

But you can avoid making future growth impossible.


Architecture Is Also Communication

Suppose a new developer opens a codebase.

Good architecture tells them a story.

They see:

Orders
Payments
Inventory
Customers

They can understand where business logic lives.

They know where database concerns belong.

They know where external integrations belong.

Bad architecture tells no story.

Everything is:

Helpers
Managers
Common
Utils
Services
Base
Misc

The developer must open every file to understand anything.

Architecture creates vocabulary.

It tells the team:

"This is where this kind of decision belongs."

That shared understanding becomes increasingly valuable as teams grow.


The Best Architecture Makes Wrong Code Harder to Write

Suppose your domain layer cannot reference infrastructure.

Then a developer cannot casually write:

new SqlConnection(...)

inside a domain entity.

The architecture prevents it.

Suppose properties have private setters and state changes happen through methods.

Then invalid transitions become harder.

Suppose payment processing is behind a contract.

Then swapping providers becomes easier.

Good architecture does not merely document what developers should do.

It shapes the system so certain mistakes become inconvenient or impossible.

That is powerful.


The Real Meaning of "Clean"

Clean Architecture does not mean:

No messy code anywhere.

Real systems always contain compromises.

Legacy integrations.

Performance optimizations.

Vendor limitations.

Temporary workarounds.

The goal is to keep those compromises away from the heart of the system whenever possible.

If a terrible third-party API requires ugly code, isolate it.

UglyVendorAdapter

Let the ugliness stop there.

Do not allow the vendor's weird concepts to infect the entire business model.

Architecture creates quarantine zones for complexity.


What Happens Without Architecture?

Nothing dramatic at first.

That is why the problem is dangerous.

The application works.

Features ship quickly.

Then slowly:

Changes take longer.

Tests become harder.

Deployments become riskier.

Developers become afraid to refactor.

More bugs appear.

New hires struggle.

Every feature requires touching more files.

Eventually management asks:

"Why does such a small change take three weeks?"

The answer is often not that the developers became slower.

The system became harder to change.

Technical debt has accumulated interest.

Architecture is one way of controlling that interest.


What Happens With Too Much Architecture?

The opposite failure is also possible.

A team spends weeks debating abstractions before delivering anything.

Every simple operation requires many layers.

Developers spend more time satisfying architecture than solving business problems.

New features become slow because the design is excessively rigid.

This is why architecture must remain pragmatic.

The purpose is not to create the purest system.

The purpose is to help software deliver value over time.

The architecture must justify its cost.


Principles Are Compressed Experience

Why do programmers talk about SOLID, DRY, separation of concerns, encapsulation, dependency inversion, and Clean Architecture?

Because generations of developers repeatedly encountered the same failures.

Huge classes became difficult to change.

Tightly coupled modules became difficult to test.

Duplicated business rules drifted apart.

Concrete dependencies made technology replacements painful.

Uncontrolled state created bugs.

These principles are not arbitrary academic rules.

They are compressed lessons from software that hurt people before us.

Following them does not guarantee good software.

Ignoring all of them usually means rediscovering the same failures yourself.


Then AI Started Writing the Code

Today an AI system can generate:

controllers
services
repositories
entities
tests
SQL
interfaces

in seconds.

That changes the economics of coding.

But it does not eliminate architecture.

It may make architecture even more important.

If generating code becomes almost free, generating too much bad code becomes almost free as well.

Someone still needs to decide:

Where does this responsibility belong?

Should this abstraction exist?

Which direction should this dependency point?

Is this business logic or infrastructure?

Will this design survive the expected changes?

AI can produce ten implementations.

Architecture determines which one belongs in the system.

Code generation reduces the cost of typing.

It does not remove the cost of complexity.


The Question Behind Every Architecture Decision

Whenever you introduce:

interface
layer
service
repository
event
module
abstraction

ask:

What change am I protecting the system from?

If you have a good answer, the abstraction probably has value.

For example:

IPaymentGateway

because payment providers may change.

IFileStorage

because storage infrastructure may change.

TaxPolicy

because tax rules vary.

But:

IStringFormatterFactoryProvider

perhaps not.

Architecture should respond to real sources of complexity.

Not imaginary prestige.


The Code Is Not the Product

A user does not care whether you used Clean Architecture.

They do not care whether your project follows SOLID.

They do not care how beautiful your dependency graph is.

They care that:

the invoice is correct
the order is processed
the system is fast
their data is safe
the service keeps working
new features arrive

Architecture exists to help us provide those outcomes repeatedly.

That is the important distinction.

Architecture is not the goal.

Sustainable software is the goal.


Why We Architect Software

Return to that small application from the beginning.

One developer.

A few screens.

A few tables.

Then years pass.

It grows into a system containing:

millions of records
hundreds of business rules
dozens of developers
multiple services
external integrations
customers depending on it every day

At that scale, code is not merely instructions for a computer.

It is a structure through which an organization stores knowledge.

Business rules.

Decisions.

Processes.

Assumptions.

History.

Architecture is how we keep that knowledge understandable.

Principles such as SOLID help us decide where responsibilities belong.

Clean Architecture reminds us to protect business rules from infrastructure.

Dependency inversion keeps high-level policy from becoming trapped inside technical details.

Separation of concerns keeps unrelated changes apart.

Encapsulation protects valid state.

Testing gives us confidence to change things.

None of this exists because elegant code is beautiful.

It exists because tomorrow somebody will change the system.

Maybe that person will be you.

Maybe a new developer.

Maybe someone five years from now who has never met the original team.

Good architecture is a message to that future programmer.

It says:

Here is where the important logic lives.

Here is what depends on what.

Here are the boundaries.

You can change this part without understanding the entire universe.

That is why we architect software.

Not to make today's code look sophisticated.

But to make tomorrow's change possible.

And that may be the most important principle of all:

Write software not only for the computer that executes it today, but for the humans who must understand and change it tomorrow.

Leave a comment

Your email address will not be published. Required fields are marked *