Documentation

adrs/049-stream-centric-zebra-rfid-architecture.md

ADR-049: Stream-Centric Zebra RFID Architecture

Status

Accepted - 2025-01-16

Context

The BBU (Basket Business Unit) component needs to process RFID tag reads from Zebra FX-series readers to track basket locations and manage delivery routes. Initially, the MQTT connection logic and message processing were tightly coupled within the BBU component, leading to several issues:

  1. Tight Coupling: MQTT infrastructure (connection, reconnection, subscriptions) mixed with business logic
  2. Code Duplication: Other components needing MQTT processing would duplicate connection logic
  3. Testing Difficulty: Hard to test business logic independently from MQTT infrastructure
  4. Session Takeover Errors: Multiple instances connecting with same client ID caused repeated disconnections
  5. Architectural Boundary Violations: BBU component directly referencing Edge component implementation
  6. Missing Audit Trail: No historical record of RFID messages for troubleshooting or replay

The TeletracNavman (TN360) integration already demonstrated a successful pattern for separating IoT infrastructure from business logic, with raw data capture to an Edge database. We wanted to apply similar principles to Zebra RFID processing.

Decision

We implemented a stream-centric architecture for Zebra RFID processing with the following key components:

1. Abstract Base Classes in Edge.Abstractions

Created two abstract base classes in Edge.Abstractions/Services/:

MqttProcessorBase<TConfig>:

  • Generic MQTT client processor handling connection management
  • Automatic reconnection with configurable delays
  • Shared subscription support for load balancing
  • Thread-safe connection handling
  • QoS 2 (Exactly Once) delivery
  • Template method pattern for extensibility

ZebraRfidProcessor<TConfig>:

  • Extends MqttProcessorBase with Zebra-specific functionality
  • Parses Zebra message formats (tag reads, management events, responses)
  • Extracts device ID from MQTT topic structure
  • Optional raw data capture to Edge database
  • Routes messages to business logic hooks

2. Raw Data Capture Pattern

Implemented audit trail pattern with three database tables in Edge database:

  • zb_tag_reads - Normalized tag read data + complete raw JSON
  • zb_management_events - Reader health events (heartbeats, errors, warnings)
  • zb_responses - Command response messages (control and management)

Design Principles:

  • 2-letter vendor prefix (zb_ for Zebra, following tn_ for TeletracNavman)
  • Store normalized fields for querying + raw JSON for replay
  • UTC DateTime for all timestamps (PostgreSQL compatibility)
  • Configurable capture options per message type
  • Retention period configuration

3. Business Logic Separation

Business components (like BBU) extend abstract base classes:

public class BbuMqttProcessor : ZebraRfidProcessor<ZebraRfidProcessorConfiguration> {
    protected override Task OnTagReadsAsync(string deviceId, List<BasicReadEvent> reads) {
        // Pure business logic - basket tracking, route management
    }
}

Result: 30% code reduction (110 lines → 77 lines) by eliminating infrastructure code.

4. Stream-Centric Abstraction

Focus on MQTT topics as the abstraction boundary, not physical devices:

  • Topic structure defines the stream: zebra/fx/{deviceId}/reads
  • Device ID extracted from topic at runtime
  • Enables dynamic device discovery
  • Shared subscriptions distribute load across instances
  • Matches existing TN360 pattern

5. Service Lifetime Management

Solved singleton-to-scoped dependency issue:

  • Processors are IHostedService (singleton lifetime)
  • Database contexts are scoped
  • Inject IServiceProvider and create scopes when needed
  • Prevents "cannot consume scoped service from singleton" errors

6. PostgreSQL DateTime Compatibility

Standardized on UTC DateTime instead of DateTimeOffset:

  • PostgreSQL timestamptz only accepts UTC (offset 0)
  • Convert with .UtcDateTime or use DateTime.UtcNow
  • Prevents runtime errors with non-UTC offsets

7. Architectural Compliance

Moved abstract base classes to .Abstractions:

  • MqttProcessorBase and ZebraRfidProcessor in Edge.Abstractions
  • Allows business components to extend without implementation dependencies
  • Maintains Dynaplex principle: components reference only .Abstractions projects

Alternatives Considered

Alternative 1: Device-Centric Architecture

Approach: Create device abstractions for each Zebra reader.

Rejected Because:

  • More complex: Need device registry and management
  • Less flexible: Hard to add/remove readers dynamically
  • Doesn't match MQTT topic structure naturally
  • Over-engineered for current needs

Alternative 2: Keep Everything in BBU

Approach: Leave all MQTT and Zebra logic in BBU component.

Rejected Because:

  • Not reusable: Other components needing Zebra integration would duplicate code
  • Violates separation of concerns: IoT infrastructure vs business logic
  • Harder to test and maintain
  • Doesn't align with TN360 pattern already established

Alternative 3: Concrete Base Class in Edge Component

Approach: Put base classes in Edge component implementation, not Edge.Abstractions.

Rejected Because:

  • Violates Dynaplex architectural boundaries
  • Creates circular dependency potential
  • Forces business components to reference implementation projects
  • Makes testing harder

Alternative 4: No Raw Data Capture

Approach: Process messages in real-time only, no historical storage.

Rejected Because:

  • No audit trail for compliance/troubleshooting
  • Cannot replay messages if processing fails
  • Cannot analyze historical patterns
  • Missing debugging capability for production issues

Consequences

Positive

  1. Code Reuse: Any component can process Zebra RFID with minimal code
  2. Separation of Concerns: Infrastructure cleanly separated from business logic
  3. Testability: Business logic easily testable without MQTT infrastructure
  4. Audit Trail: Complete message history for troubleshooting and replay
  5. Scalability: Shared subscriptions enable horizontal scaling
  6. Consistency: Follows established TN360 pattern
  7. Maintainability: Infrastructure bugs fixed once, benefits all processors
  8. Flexibility: Easy to add new message types or processing logic

Negative

  1. Abstraction Learning Curve: Developers must understand template method pattern
  2. Service Lifetime Complexity: IServiceProvider scope pattern less intuitive
  3. Database Storage: Raw data capture increases storage requirements
  4. Initial Setup: More configuration required (Edge database, MQTT settings)

Neutral

  1. Pattern Consistency: Good for organization, but requires discipline to maintain
  2. Documentation Needs: Requires comprehensive documentation (now addressed)

Implementation Notes

For Component Developers

To create a Zebra RFID processor:

  1. Extend ZebraRfidProcessor<TConfig>
  2. Inject IServiceProvider (for raw data capture)
  3. Implement OnTagReadsAsync() with business logic
  4. Register with AddHostedService<YourProcessor>()
  5. Configure in appsettings.json

For Infrastructure Developers

The base classes are in Edge.Abstractions:

  • Edge.Abstractions/Services/MqttProcessorBase.cs
  • Edge.Abstractions/Services/ZebraRfidProcessor.cs

Changes to these affect all processors - test thoroughly!

Configuration Management

  • Components: Example configs with "Name": "_example"
  • Projects: Real configs with actual credentials
  • Conditional registration based on config presence

Database Migrations

Raw data capture tables are in Edge database:

  • Run Edge migrations before starting processors
  • Use EdgeDb context for raw data access

Monitoring & Operations

Key Metrics to Monitor

  • MQTT connection status
  • Message processing rate
  • Raw data capture success rate
  • Database storage growth
  • Processing latency

Troubleshooting

Common issues and solutions documented in:

  • /engines/iot/resources/zebra-rfid-architecture.md
  • /engines/bbu/resources/implementing-mqtt-processors.md
  • ADR-007: Aspire Microservices Migration
  • ADR-XXX: (Future) IoT Data Retention Policies
  • ADR-XXX: (Future) MQTT Broker Selection and Configuration
  • TeletracNavman Pattern: Similar stream-centric architecture for TN360
  • Template Method Pattern: Base class defines algorithm, derived classes customize
  • Dependency Injection: Service lifetime management

References

Approval

  • Author: Claude (AI Assistant)
  • Reviewed By: Dan Castonguay
  • Date: 2025-01-16
  • Status: Accepted and Implemented

Revision History

Date Version Changes Author
2025-01-16 1.0 Initial ADR Claude