Customer-facing
Storefront · product catalog · configuration · account · order history · shipment tracking · Zibi ownership & configuration · warranty · support
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:
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.
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.
Five representative partners show different forms of coupling. Each implementation solved an immediate business need. The coupling became visible later.
Checkout calls PayPaw inline, so the order waits on it. Provider states
like PendingUnderwriting live in internal models.
The first specialized carrier, so its handling fields landed in the core
shipment model. An if carrier == HWC check sits in shipping logic.
Nightly file exchange, older than the API era. Some files are generated straight from DB structures, so the schema shape leaks outward.
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.
Built into Zibi Lifecycle so pairing could ship fast. Its callbacks move internal Zibi state, blurring paired / connected / authorized.
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
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.
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.