Skip to content

The Starting Point

The business grew quickly. The platform grew with it.

ZibiWorks began as a straightforward web application. Over years of success it became the operational center of the company, the place orders, customers, Zibis, inventory, and partners all come together.

This is the platform as it stands before any modernization: a simple architecture that worked well, grew organically, and has since been outgrown by the business it supports.

Customers and employees use different presentation surfaces, but both converge on the same business tier, data tier, and shared SQL Server database. Around that core are several systems that have grown somewhat independently: partner integrations, connected-device telemetry and its data store, manufacturing and warehouse systems, and a separate reporting database used by analytics and BI users.

flowchart TB
    Cust["Customers"]
    Emp["Employees"]
    CustWeb["Customer Web App"]
    OpsWeb["Internal / Operations Web App"]
    Biz["Business Tier"]
    Data["Data Tier"]
    DB[("Operational SQL Server")]

    Cust --> CustWeb --> Biz
    Emp --> OpsWeb --> Biz
    Biz --> Data --> DB

    Partners["Partner Integrations"]
    Ops["Manufacturing / Warehouse Systems"]
    Partners -.-> Biz
    Ops -.-> Biz

    Zibis["Connected Zibis"]
    Gateway["Device Gateway"]
    Tele["Telemetry Pipeline"]
    TeleStore[("Telemetry Data Store")]
    Zibis --> Gateway --> Tele --> TeleStore
    Tele -.-> Biz

    Rep[("Reporting Database")]
    BI["BI / Reporting Tools"]
    Analysts["Analysts / BI Users"]
    DB -->|"nightly ETL"| Rep --> BI --> Analysts
    DB -.->|"some reports query directly"| BI

Customers and employees use different presentation surfaces, but both converge on the same application stack, business logic, and shared data model. There is no native mobile app; the customer and internal experiences are both web applications. They look and behave differently, but behind them sits one shared backend.

Customer-facing

Storefront · product catalog · configuration · account · order history · shipment tracking · Zibi ownership & configuration · warranty · support

Internal / operations

Customer service · order & pricing admin · fulfillment · inventory · shipping exceptions · returns · device support · Zibi diagnostics · operational exception handling

The technical tiers are clean and easy to follow. The intended path runs straight down the stack:

ControllerMVC pages & controller-based HTTP endpoints
↓
Business Servicefunctional business logic
↓
Repository / Data Accessqueries & persistence
↓
Entity FrameworkORM mapping
↓
SQL Servershared operational schema

Dependency injection, background workers, scheduled jobs, REST integrations, and file-based integrations are all present, along with a few newer satellite components. From a traditional n-tier perspective, this still looks disciplined.

Over time, persistence models gradually became application models. Entity Framework entities are returned from repositories, passed through business services, and surfaced to controllers. The controller layer ends up depending on persistence-shaped types.

How it flows

Repositories return EF entities → services pass them along → controllers depend on their shape and navigation properties. DTOs exist on some newer endpoints, but usage is inconsistent.

What to notice

Include() chains span several business concerns · navigation properties cross domain boundaries · entities carry more than an operation needs · controller behavior tracks entity shape.

This was a convenient shortcut when there was one primary application and few consumers. It quietly couples the database schema, the EF model, business logic, and controller contracts together.

The technical layers are fairly clean. The business boundaries are not. The business tier has recognizable functional areas, but they call one another directly and share models, so the boundaries between them have blurred.

flowchart LR
    Product["Product / Catalog"]
    Pricing["Pricing"]
    Orders["Orders"]
    Payments["Payments"]
    Inventory["Inventory"]
    Fulfillment["Fulfillment"]
    Shipping["Shipping"]
    Customer["Customer"]
    Zibi["Zibi Lifecycle"]

    Orders --> Customer
    Orders --> Pricing
    Orders --> Inventory
    Orders --> Payments
    Orders --> Shipping
    Product --> Orders
    Inventory --> Fulfillment
    Fulfillment --> Shipping
    Shipping --> Orders
    Zibi --> Customer
    Zibi --> Orders
    Payments --> Orders

Each individual dependency is reasonable. An Order genuinely needs pricing, inventory, payment, and shipping. The difficulty is the accumulated network: few areas can change, deploy, or be reasoned about in isolation.

Data access is centralized enough to look disciplined, but ownership is ambiguous. Many business areas read and update the same tables, and the schema does not line up with the business-domain map from the Meet ZibiWorks page.

Several tables started with a clear meaning and gradually absorbed responsibilities from multiple domains. Their columns are precise. Their business meaning has drifted.

Customer

  • purchaser
  • billing & shipping contact
  • household
  • Zibi owner
  • support contact
  • financing & smart-home identity

Product

  • retail SKU
  • product model
  • manufacturing SKU
  • firmware family
  • shipping & battery attributes
  • partner category codes

Order

  • direct customer order
  • retailer order
  • replacement order
  • fulfillment request
  • warranty replacement
  • return-related transaction

Address

  • billing
  • shipping
  • household location
  • warehouse
  • repair location
  • partner destination

Status (shared code table)

  • order status
  • payment status
  • shipment status
  • return status
  • Zibi status

The codebase includes shared libraries (Common.Models, Common.Business, Common.Data, Common.Utilities) introduced to avoid duplication. Over time, types like Common.Models.Customer, Common.Models.Product, and Common.Models.Address became shared application concepts.

Reuse reduced duplication, but increased semantic coupling.

The shared libraries were a reasonable idea. The coupling comes from what grew to depend on these particular shared shapes.

Partner integrations grew where they were needed

Section titled “Partner integrations grew where they were needed”

Five representative partners show different forms of coupling. Each implementation solved an immediate business need. The coupling became visible later.

PayPaw

synchronous API · workflow coupling

Checkout calls PayPaw inline, so the order waits on it. Provider states like PendingUnderwriting live in internal models.

Handle With Care

API · partner-model leakage

The first specialized carrier, so its handling fields landed in the core shipment model. An if carrier == HWC check sits in shipping logic.

Paw & Circuit

SFTP batch · schema coupling

Nightly file exchange, older than the API era. Some files are generated straight from DB structures, so the schema shape leaks outward.

RoboMotion Systems

supplier-identifier leakage

Its part numbers were the easy way to track mobility hardware, so they spread into manufacturing, inventory, and diagnostics. That holds up until a second supplier appears.

HomeSphere

OAuth callbacks · external-state coupling

Built into Zibi Lifecycle so pairing could ship fast. Its callbacks move internal Zibi state, blurring paired / connected / authorized.

Integration logic lives inside business services

Section titled “Integration logic lives inside business services”

There is no central integration platform or consistent anti-corruption layer around external systems. Partner-specific behavior accumulated wherever a team needed it, mixed in with ordinary business logic.

That means external contracts, terminology, and quirks can leak directly into the core application, increasing coupling and making partner changes harder to isolate.

flowchart TB
    subgraph os ["OrderService"]
        A["PayPawClient"]
        B["HwcShipmentLogic"]
        C["PawCircuitOrderLogic"]
    end
    subgraph is ["InventoryService"]
        D["PawCircuitInventoryExport"]
        E["RoboMotionPartMapping"]
    end
    subgraph cs ["ZibiService"]
        F["HomeSphereAuthorization"]
        G["RoboMotionDiagnosticsMapping"]
    end

The Zibi already spans more than one system

Section titled “The Zibi already spans more than one system”

Device connectivity and high-volume telemetry already operate somewhat independently because their scale and technology demands pushed them there. The core still owns transactional state such as identity, ownership, pairing, and target firmware, while the telemetry platform knows what the device is actually doing.

flowchart TB
    Zibi(["A Zibi"])
    Core["Core Platform<br/>transactional state"]
    Tele["Telemetry Platform<br/>observed state"]
    CoreState["identity · ownership<br/>pairing · target firmware"]
    TeleState["actual firmware · connectivity<br/>battery · sensor telemetry · health"]

    Zibi --> Core
    Zibi --> Tele
    Core --- CoreState
    Tele --- TeleState

That leaves ZibiWorks with two views of the same Zibi. Reconciling those views, and deciding where different kinds of state should belong, remains an open question.

The platform is not purely synchronous. Asynchronous mechanisms were added where needed, though without a single consistent enterprise pattern.

Background & scheduled

Background workers · scheduled jobs · nightly batch · retry jobs

Integration & reporting

Partner-file generation · reconciliation · reporting extracts

Reporting started as queries against the operational database. Pressure later introduced a nightly ETL into a separate reporting database for BI tools, but the coupling didn’t fully go away.

flowchart LR
    Op[("Operational SQL Server")]
    Rep[("Reporting Database")]
    BI["BI Tools"]
    Direct["Some operational reports"]

    Op -->|"nightly ETL"| Rep --> BI
    Op -.->|"still queried directly"| Direct

Some important reports still hit operational data directly, the reporting copy can be stale, and reporting structures depend heavily on the shared schema. Analytical and operational concerns still interfere with one another.

The platform still ships largely together, on traditional virtualized infrastructure.

flowchart TB
    LB["Load Balancer"]
    A1["App Server 1"]
    A2["App Server 2"]
    DB[("SQL Server")]

    LB --> A1
    LB --> A2
    A1 --> DB
    A2 --> DB

There is room, later, to talk about rehosting, replatforming, refactoring, Strangler Fig, cloud migration, and deployment independence. None of that has happened yet.

For a long time, this architecture was a good fit for ZibiWorks.

Simple to reason about

One deployment, one database, one mental model. Low operational complexity.

Strong data guarantees

Straightforward transactions, relational integrity, and easy joins across the whole business.

Fast early on

Shared models made reuse easy and features shipped quickly. Mature .NET tooling all the way down.

One place for everything

A single shared backend served both the customer and internal experiences; direct integrations let new partnerships launch fast.

The same decisions that once simplified the system are now the ones that create friction. As ZibiWorks grew, the tradeoffs changed.

What used to simplify the system

  • one shared backend behind every experience
  • shared models & shared database
  • direct calls between business areas
  • direct, in-place partner integrations
  • one coordinated deployment
→

What now creates friction

  • customer & internal concerns entangled
  • ambiguous ownership & blurred meaning
  • cross-domain coupling; little isolation
  • vendor specifics inside business logic
  • changes ripple; deploys must be coordinated

This page described what ZibiWorks built. The platform works, supports the business, and has real strengths. It also has coupling and ambiguity that make it increasingly hard to change one part without touching others.

That sets up the first real decision of the series:

What should ZibiWorks change first, and what should it leave alone?

Nothing has been decided. Decomposition, microservices, cloud migration, and every other path are still open questions, to be weighed against the pressures described here.