Incremental Decomposition of a Live Runtime System

Modern systems rarely begin with perfect architecture.

Most real systems evolve from:

  • a working prototype,
  • an operational script,
  • a single service,
  • or a growing runtime loop.

The real engineering challenge is not building a perfect greenfield design.

The real challenge is:

evolving a live operational system safely without breaking it.

That process is what I call:

Incremental Decomposition of a Live Runtime System

The Common Trap

Many developers eventually hit this moment:

“This service became too large.”

Then the dangerous ideas start:

  • “Let’s rewrite everything.”
  • “Let’s implement Clean Architecture.”
  • “Let’s rebuild using microservices.”
  • “Let’s move to CQRS/Event Sourcing.”

Most systems fail here.

Why?

Because:

  • operational behavior is already working,
  • runtime assumptions already exist,
  • hidden coupling already formed,
  • production logic already evolved organically.

Large rewrites usually introduce:

  • instability,
  • regressions,
  • unclear ownership,
  • endless refactor cycles.

A Better Approach

Instead of rewriting:

progressively extract responsibilities.

One boundary at a time.

One stable contract at a time.

One operational behavior at a time.


Real Example — Trading Runtime Evolution

A trading bot often starts like this:

Program.cs
-> fetch data
-> generate signal
-> validate risk
-> place order
-> update state
-> log everything

At first this is fine.

But eventually:

  • stop-loss logic grows,
  • portfolio rules grow,
  • runtime recovery appears,
  • execution tracking appears,
  • reconciliation becomes necessary.

Now the single service becomes:

operationally dense.


The Wrong Move

The wrong response is:

“Rewrite the entire platform.”

The correct response is:

“What responsibility can be safely extracted next?”

The Decomposition Pattern

A mature decomposition sequence often looks like:

Step 1 — Separate Signal Generation

Strategy
decides

TradingService
orchestrates

Step 2 — Separate Risk Governance

RiskEngine
validates

TradingService
gathers runtime context

Step 3 — Separate Execution

ExecutionService
places broker orders

Step 4 — Separate Lifecycle Tracking

TradeLifecycleService
records audit trail

Step 5 — Separate Runtime State

PositionStateService
manages runtime transitions

Step 6 — Separate Recovery

RecoveryService
reconciles broker/runtime state

Step 7 — Separate Runtime Coordination

TradingRuntimeService
owns orchestration loop

The Key Insight

Notice something important:

No rewrite occurred.

The runtime stayed operational the entire time.

That is critical.

Because architecture should evolve:

under operational pressure.

Not in isolation.


Why Incremental Decomposition Works

This approach provides:

1. Operational Stability

The system continues running while architecture improves.


2. Smaller Blast Radius

Each extraction changes only one responsibility.

Failures become easier to isolate.


3. Better Runtime Understanding

You discover real system boundaries from:

  • runtime behavior,
  • operational pain,
  • scaling pressure,
  • recovery needs.

Not from theoretical diagrams.


4. Cleaner Ownership

Eventually the system becomes:

Runtime Coordinator
orchestrates

Governance Services
validate

Workflow Services
coordinate

Execution Services
execute

Recovery Services
reconcile

At that point:

  • reasoning improves,
  • testing improves,
  • extensibility improves,
  • future capabilities emerge naturally.

The Most Important Engineering Skill

Most developers learn:

  • frameworks,
  • patterns,
  • syntax.

Far fewer learn:

controlled evolution of operational systems.

That skill matters more in real engineering environments.

Because most enterprise systems are not rewritten.

They evolve.


When To Stop Refactoring

This is equally important.

Eventually you reach:

diminishing returns.

At that point:

  • stop extracting services,
  • stop renaming abstractions,
  • stop chasing “perfect architecture.”

Instead:

  • run the system,
  • observe failures,
  • validate recovery,
  • analyze logs,
  • study runtime behavior.

Operational pressure should guide the next evolution.


Final Thought

Good architecture is not:

  • maximum abstraction,
  • maximum patterns,
  • or maximum complexity.

Good architecture is:

clear responsibility boundaries that evolved safely under real operational conditions.

That is how live runtime systems mature professionally.

Building a Minimal Yet Serious Trading Platform Architecture

Introduction

Most trading bot tutorials start with a single console application and slowly evolve into unmaintainable complexity:

  • trading logic mixed with broker code
  • logging scattered everywhere
  • global runtime state
  • no lifecycle tracking
  • no operational telemetry
  • no execution governance

At the other extreme, many architecture discussions immediately jump into:

  • microservices
  • CQRS
  • event sourcing
  • distributed actors
  • Kubernetes
  • enterprise-level abstraction layers

Neither extreme is ideal for an MVP trading platform.

This article walks through the architecture evolution of a lightweight but serious trading system built with:

  • C#
  • .NET
  • Alpaca API
  • Azure-ready deployment patterns

The goal was simple:

Build an architecture strong enough to evolve into a SaaS trading platform later, without overengineering the MVP.


The Core Philosophy

The architecture intentionally favors:

  • practical layering
  • operational clarity
  • explainable execution
  • incremental evolution
  • execution-aware telemetry
  • runtime correctness
  • low ceremony

The system intentionally avoids:

  • premature distributed systems
  • unnecessary abstractions
  • architecture for architecture’s sake
  • enterprise-pattern overload

The focus is:

Build only what real runtime pressure requires.


Final Architecture

Tanolis.Trading.Console

Tanolis.Trading.Core.Domain

Tanolis.Trading.Core.Services

Tanolis.Trading.Infrastructure


Dependency Structure

Console

↓ references

Core.Services

↓ references

Core.Domain

Infrastructure

↓ references

Core.Services

↓ references

Core.Domain

Additionally:

Console

↓ references

Infrastructure


Visual Architecture Shape

Console


Layer Responsibilities

Tanolis.Trading.Console

Purpose:

  • runtime host
  • execution scheduler
  • startup/bootstrap
  • configuration loading
  • dependency wiring

Examples:

  • Program.cs
  • execution timers
  • appsettings loading
  • runtime startup

The console project should NOT contain:

  • trading logic
  • broker implementation logic
  • persistence logic
  • lifecycle management

Tanolis.Trading.Core.Domain

Purpose:

  • business meaning
  • trading vocabulary
  • lifecycle models
  • domain constants
  • runtime state models

Examples:

  • TradeRecord
  • TradeSignal
  • BotState
  • SymbolState
  • ExitReasons
  • TradeActions
  • LogEvents
  • LogLevel
  • LogSources

The domain layer intentionally remains independent of:

  • Alpaca SDK
  • Azure SDKs
  • SQL Server
  • runtime hosting
  • logging implementations
  • persistence implementations

This keeps the business concepts clean and portable.


Tanolis.Trading.Core.Services

Purpose:

  • execution orchestration
  • trading workflows
  • lifecycle coordination
  • risk management
  • application contracts
  • runtime coordination

Examples:

  • TradingService
  • StateService
  • TradeJournalService
  • SmaStrategy
  • RuntimeContext
  • IBroker
  • ILogService

Suggested internal structure:

Core.Services

Contracts

IBroker.cs

ILogService.cs

Configuration

TradingConfig.cs

AlpacaConfig.cs

Models

BrokerOrderResultDto.cs

OrderDto.cs

Trading

TradingService.cs

Strategies

SmaStrategy.cs

State

StateService.cs

Journaling

TradeJournalService.cs

Runtime

RuntimeContext.cs

Core.Services acts as the orchestration and application behavior layer.


Tanolis.Trading.Infrastructure

Purpose:

  • external integrations
  • broker connectivity
  • logging implementations
  • operational integrations
  • persistence implementations

Examples:

  • AlpacaBroker
  • LogService
  • Azure integrations
  • future SQL implementations
  • future Table Storage implementations

Infrastructure implements application contracts defined by Core.Services.

Example:

publicclassAlpacaBroker : IBroker

and:

publicclassLogService : ILogService

This creates clean dependency inversion while keeping the architecture lightweight.


Ports and Adapters Direction

One interesting architectural observation was that the platform naturally evolved toward:

Ports and Adapters

without intentionally overengineering for it.

Current mapping:

LayerRole
Core.Servicesports/contracts
Infrastructureadapters
Core.Domainbusiness concepts
Consolecomposition root

This created a clean separation between:

  • business intent
  • execution orchestration
  • external implementations
  • runtime hosting

without introducing unnecessary complexity.


Operational Telemetry Philosophy

One major architectural decision was treating logs as:

operational decision telemetry

instead of simple debug output.

This changed the entire design approach.

The system now tracks:

CategoryPurpose
Signal telemetrywhy signals occurred
Execution telemetrywhy trades executed or were blocked
Risk telemetrygovernance decisions
Lifecycle telemetrytrade continuity
Runtime telemetryoperational health

Examples:

TradeBlocked | DailyLossLimit

TradeBlocked | OpenOrderExists

TradeSkipped | SidewaysMarket

StateReconciled | Clearing stale state

This dramatically improves:

  • debugging
  • strategy analysis
  • operational trust
  • SaaS observability
  • future analytics

Symbol-Scoped Runtime State

One of the most important architectural evolutions was moving from:

global runtime state

to:

symbol-scoped runtime state

Originally the bot stored:

one EntryPrice

one ActiveTradeId

one LastTradeTime

for ALL symbols.

This worked initially but became a serious correctness problem once the bot traded:

  • AAPL
  • MSFT
  • NVDA

simultaneously.

The solution was introducing:

Dictionary<string, SymbolState>

inside:

BotState

Each symbol now maintains:

  • isolated trade lifecycle
  • isolated cooldowns
  • isolated stop-loss state
  • isolated reconciliation state
  • isolated PnL tracking

This was one of the most important runtime architecture corrections in the platform.


Runtime Metadata and Trade Lifecycle Tracking

The platform also evolved into execution-aware lifecycle tracking.

The system now tracks:

IdentifierPurpose
SessionIdbot runtime instance
CycleIdexecution loop iteration
TradeIdtrade lifecycle
OrderIdbroker execution

This enables:

  • execution tracing
  • operational diagnostics
  • auditability
  • lifecycle analytics
  • future distributed execution support

without prematurely implementing distributed systems.


Why Minimal Architecture Matters

The biggest lesson from this architecture journey was:

Minimal architecture does NOT mean weak architecture.

The platform now supports:

  • multi-symbol execution
  • isolated runtime state
  • execution governance
  • structured telemetry
  • lifecycle tracing
  • broker abstraction
  • operational reconciliation
  • future cloud hosting
  • SaaS evolution

while still remaining:

  • understandable
  • lightweight
  • maintainable
  • incremental

Future Direction

The current architecture is intentionally designed to evolve gradually toward:

  • Azure Container Apps
  • Azure Table Storage telemetry
  • SQL Server + EF Core persistence
  • Web API exposure
  • Blazor dashboards
  • analytics and reporting
  • multi-user SaaS support
  • distributed runtime workers

The key principle is:

Only evolve architecture when real operational pressure justifies it.


Final Thoughts

A successful MVP architecture is not the one with the most patterns.

It is the one that:

  • survives growth
  • remains understandable
  • supports operational visibility
  • evolves incrementally
  • avoids unnecessary complexity

This trading platform architecture intentionally focused on:

practical engineering over architectural theater

And that balance turned out to be far more valuable than prematurely chasing enterprise complexity.

Why Web APIs Don’t Switch Environments at Runtime

A common misconception in modern web development is that a Web API can dynamically switch between environments—such as Test and Production—based on a runtime signal like a request header or UI selection. In practice, this is not how ASP.NET Core (or most backend frameworks) are designed to operate.

The Core Principle

When a Web API starts, it is initialized with a specific environment:

ASPNETCORE_ENVIRONMENT = Development | Test | Production

This environment determines:

  • Which configuration files are loaded (appsettings.{env}.json)
  • Connection strings and external resources
  • Logging behavior and security settings
  • Feature toggles and integrations

👉 This configuration is fixed at application startup and cannot be changed per request.


Why Runtime Switching Doesn’t Work

Even if a client sends something like:

X-Environment: Production

the API will still:

  • Use the configuration it loaded at startup
  • Connect to the same databases and services
  • Execute logic based on its deployed environment

In other words:

A request can express intent, but it cannot override the API’s runtime environment.


Common Misunderstanding

Developers often attempt to:

  • Add an environment dropdown in the UI
  • Pass the selected value via headers
  • Expect the backend to “switch” environments

This leads to confusion when:

  • Test works as expected
  • Production appears unresponsive or unchanged

Because the backend is still running in its original environment.


Correct Architectural Approaches

There are three valid patterns:

1. Separate Deployments (Recommended)

  • Test UI → Test API
  • Production UI → Production API

✔ Safe
✔ Standard
✔ Aligned with enterprise practices


2. Environment-Aware Logic (Advanced)

  • Use headers or parameters to route behavior manually
  • Maintain separate configs inside the same app

⚠ Complex and risky
⚠ Requires strict safeguards


3. Hybrid (Best for Operations Tools)

  • Backend environment remains fixed
  • UI shows environment context
  • Headers used for logging, validation, or guardrails

✔ Safe
✔ Flexible
✔ Practical


Key Takeaway

A Web API’s environment is a deployment concern, not a runtime switch.

Trying to dynamically switch environments at runtime can lead to:

  • Incorrect data access
  • Security risks
  • Unintended production actions

Final Thought

Instead of forcing runtime switching, design your system so that:

  • Environments are clearly separated
  • UI reflects environment context
  • Safety mechanisms protect production

This approach is not only more reliable—it’s essential for systems operating in regulated or high-risk domains.

Most .NET Developers Allocate Memory They Don’t Need To — Span Fixes That

If you work with .NET long enough, you eventually discover that performance issues rarely come from complex algorithms.

They come from small allocations happening millions of times.

And many of those allocations come from code that looks perfectly harmless.


The Hidden Problem: Unnecessary Heap Allocations

Consider common operations like:

  • .Substring()
  • .Split()
  • .ToArray()

These methods feel lightweight, but each one creates new objects on the heap.

That means:

  • More memory usage
  • More work for the garbage collector
  • More latency under load

In an API handling thousands of requests — or inside a tight parsing loop — these tiny costs accumulate quickly.


Enter Span<T>

Span<T> solves this by letting you work with existing memory instead of allocating new memory.

Think of it as:

A lightweight window into data that already exists.

No copying.
No allocations.
No extra GC pressure.


A Simple Example

Imagine you have a date string:

string date = "2025-06-15";

Most developers extract the year like this:

var year = date.Substring(0, 4);

This creates a brand-new string "2025" on the heap.

Now compare that with:

ReadOnlySpan<char> year = date.AsSpan(0, 4);

Same logical result — but zero allocation.

You’re simply pointing to a slice of the original string.


The Core Mental Model

A Span<T> does not own memory.

It only references memory that already exists.

Think of it like:

Original data  ───────────────
[ window ]
Span

You move the window around instead of copying the data.


Where Span<T> Really Shines

Once you understand the concept, you’ll start seeing opportunities everywhere:

Parsing workloads

  • CSV or log file parsing without generating thousands of temporary strings.

HTTP processing

  • Parse headers without copying byte arrays.

Binary protocols

  • Slice buffers directly instead of creating intermediate arrays.

String processing

  • Replace Split() calls that create multiple arrays and strings.

Real-World Impact

In production parsing-heavy services, teams commonly see:

  • 40–60% fewer allocations
  • Noticeably reduced GC pauses
  • Higher throughput under load

Less copying means more CPU time spent doing real work.


The Three Rules You Need to Remember

1️⃣ Span<T> lives on the stack

You cannot store it on the heap or in class fields.

2️⃣ Use ReadOnlySpan<T> for read-only data

Most string scenarios fall into this category.

3️⃣ Use Memory<T> when persistence is required

If you need to store or pass the reference beyond stack scope, use Memory<T>.


How to Adopt It Without a Big Rewrite

You don’t need to refactor your entire codebase.

Start small:

  1. Profile your application
  2. Identify hot paths
  3. Look for repeated Substring, Split, or ToArray calls
  4. Replace them with Span slicing
  5. Measure again

Performance improvements here are often immediate and measurable.


Final Thought

Most .NET performance problems aren’t about writing clever code.

They’re about avoiding unnecessary work.

Span<T> gives you a simple, safe way to reduce allocations and let your application scale more efficiently — without changing how your logic works.

Once you start using it in hot paths, it becomes difficult to go back.

The Modern .NET Developer in 2026: From Code Writer to System Builder

There was a time when being a .NET developer mostly meant writing solid C# code, building APIs, and shipping features. If the application worked and the database queries were fast enough, the job was done.

That world is gone.

In 2026, a modern .NET developer isn’t just a coder. They’re a system builder, balancing application development, cloud architecture, DevOps, security, and increasingly, AI-driven decisions.

One Feature, Many Disciplines

Consider a typical modern feature:

  • A scheduled job populates data into a database.
  • That data feeds reporting tools like Power BI.
  • Deployment pipelines push updates across environments worldwide.
  • Cloud services scale automatically under load.
  • Monitoring and security controls are part of the delivery.

One feature now touches multiple domains. Delivering it requires understanding infrastructure, automation, data, deployment, and operations—not just application logic.

The scope of the role has expanded dramatically.

Fundamentals Still Matter

Despite all the change, the core skills haven’t disappeared.

Developers still need to:

  • Build REST APIs that handle real-world load
  • Write efficient Entity Framework queries
  • Understand async/await and concurrency
  • Maintain clean, maintainable codebases

Bad fundamentals still break systems, regardless of how modern the infrastructure is.

But fundamentals alone are no longer enough.

Cloud Decisions Are Now Developer Decisions

In many teams, developers now influence—or directly make—architecture decisions:

  • Should this workload run in App Service, Containers, or Functions?
  • Should data live in SQL Server or Cosmos DB?
  • Do we need messaging via Service Bus or event-driven patterns?

These choices affect cost, scalability, reliability, and operational complexity. Developers increasingly need architectural awareness, not just coding ability.

DevOps Is Part of the Job

Deployment is no longer someone else’s responsibility.

Modern developers are expected to:

  • Build CI/CD pipelines that deploy automatically
  • Containerize services using Docker
  • Ensure logs, metrics, and monitoring are available
  • Support production reliability

The boundary between development and operations has largely disappeared.

Security Is Developer-Owned

Security has shifted left.

Developers now regularly deal with:

  • OAuth and identity flows
  • Microsoft Entra ID integration
  • Secure data handling
  • API protection and access control

Security mistakes are expensive, and modern developers are expected to understand the implications of their implementations.

AI Changes How We Work

Another shift is happening quietly.

In the past, developers searched for how to implement something. Today, AI tools increasingly help answer higher-level questions:

  • What are the long-term tradeoffs of this architecture?
  • How will this scale?
  • What operational risks am I introducing?

The developer’s role moves from solving isolated technical problems to designing sustainable systems.

From Specialist to Swiss Army Knife

The modern .NET developer is no longer just a backend specialist. They are expected to be adaptable:

  • Application developer
  • Cloud architect
  • DevOps contributor
  • Security implementer
  • Systems thinker

Not every developer must master every area—but awareness across domains is increasingly required.

The New Reality

The job has evolved from writing features to building systems.

And while that can feel overwhelming, it’s also exciting. Developers now influence architecture, scalability, reliability, and user experience at a system-wide level.

The industry hasn’t just changed what we build.

It’s changed what it means to be a developer.

And in 2026, being versatile isn’t optional—it’s the job.