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:
- Tight Coupling: MQTT infrastructure (connection, reconnection, subscriptions) mixed with business logic
- Code Duplication: Other components needing MQTT processing would duplicate connection logic
- Testing Difficulty: Hard to test business logic independently from MQTT infrastructure
- Session Takeover Errors: Multiple instances connecting with same client ID caused repeated disconnections
- Architectural Boundary Violations: BBU component directly referencing Edge component implementation
- 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 JSONzb_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, followingtn_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
IServiceProviderand create scopes when needed - Prevents "cannot consume scoped service from singleton" errors
6. PostgreSQL DateTime Compatibility
Standardized on UTC DateTime instead of DateTimeOffset:
- PostgreSQL
timestamptzonly accepts UTC (offset 0) - Convert with
.UtcDateTimeor useDateTime.UtcNow - Prevents runtime errors with non-UTC offsets
7. Architectural Compliance
Moved abstract base classes to .Abstractions:
MqttProcessorBaseandZebraRfidProcessorinEdge.Abstractions- Allows business components to extend without implementation dependencies
- Maintains Dynaplex principle: components reference only
.Abstractionsprojects
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
- Code Reuse: Any component can process Zebra RFID with minimal code
- Separation of Concerns: Infrastructure cleanly separated from business logic
- Testability: Business logic easily testable without MQTT infrastructure
- Audit Trail: Complete message history for troubleshooting and replay
- Scalability: Shared subscriptions enable horizontal scaling
- Consistency: Follows established TN360 pattern
- Maintainability: Infrastructure bugs fixed once, benefits all processors
- Flexibility: Easy to add new message types or processing logic
Negative
- Abstraction Learning Curve: Developers must understand template method pattern
- Service Lifetime Complexity: IServiceProvider scope pattern less intuitive
- Database Storage: Raw data capture increases storage requirements
- Initial Setup: More configuration required (Edge database, MQTT settings)
Neutral
- Pattern Consistency: Good for organization, but requires discipline to maintain
- Documentation Needs: Requires comprehensive documentation (now addressed)
Implementation Notes
For Component Developers
To create a Zebra RFID processor:
- Extend
ZebraRfidProcessor<TConfig> - Inject
IServiceProvider(for raw data capture) - Implement
OnTagReadsAsync()with business logic - Register with
AddHostedService<YourProcessor>() - Configure in appsettings.json
For Infrastructure Developers
The base classes are in Edge.Abstractions:
Edge.Abstractions/Services/MqttProcessorBase.csEdge.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
EdgeDbcontext 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
Related ADRs
- ADR-007: Aspire Microservices Migration
- ADR-XXX: (Future) IoT Data Retention Policies
- ADR-XXX: (Future) MQTT Broker Selection and Configuration
Related Patterns
- TeletracNavman Pattern: Similar stream-centric architecture for TN360
- Template Method Pattern: Base class defines algorithm, derived classes customize
- Dependency Injection: Service lifetime management
References
- MQTTnet Documentation: https://github.com/dotnet/MQTTnet
- Zebra FX9600 Documentation: https://www.zebra.com/fx9600
- MQTT Shared Subscriptions: https://www.emqx.com/en/blog/introduction-to-mqtt5-protocol-shared-subscription
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 |