Architecture Evolution Plan: Monolith to Modular Services
- 1. Executive Summary
- 2. Current Architecture (As-Is)
- 3. Target Architecture (To-Be)
- 4. Transformation Roadmap
- 5. Phase Details
- 5.1. Phase 1: Shared Libraries & Clean Interfaces
- 5.2. Phase 2: Device Flow Layering
- 5.3. Phase 3: SOAP to REST API Modernization
- 5.4. Phase 4: Kafka for Event Bus & Inter-Node Communication
- 5.5. Phase 5: Redis Replacing Hazelcast
- 5.6. Phase 6: Service Extraction
- 5.7. Phase 7: ClickHouse Expansion for Historical Data
- 6. Cross-Phase Concerns
- 7. Risk Matrix
- 8. Decision Log
- 9. Phase Completion Checklist
- 10. Appendix A: Current Hazelcast Cache Inventory
- 11. Appendix B: Event Type Inventory
1. Executive Summary
This document describes the phased evolution of OneIoT ACS from a monolithic architecture to a modular, independently scalable service-oriented system. The plan addresses six strategic goals:
-
Decrease complexity — Break the 80+ Hazelcast cache maps, 162 SOAP methods, and circular module dependencies into bounded contexts
-
Prepare for test coverage — Introduce testable interfaces, contract tests, and per-module unit test isolation
-
Improve performance — Replace in-process event bus and Hazelcast with Kafka + Redis for predictable throughput
-
Better load balancing — Enable per-service scaling instead of scaling the entire monolith
-
Performance formula — Isolate services so capacity can be modeled as
capacity = f(service_instances, scenario_throughput) -
Stability — Each phase is independently deployable and reversible
2. Current Architecture (As-Is)
2.1. Key Problems in Current Architecture
| Problem | Description | Severity |
|---|---|---|
God Objects |
|
HIGH |
Circular Dependencies |
|
HIGH |
No Module Isolation |
Every module depends on |
HIGH |
In-Process Events |
|
MEDIUM |
SOAP Node-to-Node |
|
MEDIUM |
Hazelcast Overloaded |
Used simultaneously as cache, distributed lock manager, pub/sub message bus, and task executor. |
HIGH |
Untestable |
Modules cannot be unit-tested in isolation due to shared Hazelcast state and tight coupling. |
HIGH |
No Performance Formula |
All services run in one JVM — impossible to attribute CPU/memory to specific scenarios. |
HIGH |
5. Phase Details
5.1. Phase 1: Shared Libraries & Clean Interfaces
5.1.1. Objective
Extract shared entities and DTOs into standalone libraries. Define service interface contracts (API JARs) for each domain module. Break circular dependencies.
5.1.3. Changes
| Change | Details |
|---|---|
Create |
Extract from |
Create |
For each domain: |
Break |
|
Refactor |
Split into domain-specific cache interfaces: |
5.1.4. Impact Assessment
| Area | Impact | Description | Risk |
|---|---|---|---|
Codebase |
HIGH |
Every module’s POM changes; imports shift from |
LOW — compile-time changes only |
Runtime behavior |
NONE |
No behavioral change — same code runs, just reorganized |
NONE |
Build pipeline |
MEDIUM |
New Maven modules, changed build order, new artifact publishing |
LOW |
Database |
NONE |
No schema changes |
NONE |
Deployment |
NONE |
Still a single WAR — internal structure only |
NONE |
5.1.5. Test Coverage Plan
| Test Type | What to Cover | Tools |
|---|---|---|
Compilation tests |
Verify all modules compile with new dependency graph. Verify no missing imports after extraction. |
|
Interface contract tests |
Verify every |
JUnit 5 — one test per interface that instantiates the implementation |
Dependency graph validation |
Assert no circular dependencies exist. Assert |
Maven Enforcer Plugin ( |
Integration smoke tests |
Full application startup with the new module structure. Verify all Spring beans wire correctly. |
Spring Boot integration test ( |
Regression pack |
Run all existing tests (if any) to confirm zero behavioral change. |
Existing test suite |
// Dependency graph test (maven-enforcer or custom) @Test void noCircularDependencies() // Verify DAG in module graph // Interface contract tests @Test void eventServiceInterfaceHasImplementation() @Test void updateGroupServiceInterfaceHasImplementation() @Test void qoeServiceInterfaceHasImplementation() @Test void provisionApiInterfaceHasImplementation() // Startup smoke test @SpringBootTest @Test void applicationContextLoads() // Cache interface tests @Test void deviceCacheServiceReturnsCorrectTypes() @Test void parameterCacheServiceReturnsCorrectTypes()
5.2. Phase 2: Device Flow Layering
5.2.1. Objective
Extract the common device communication pipeline into explicit, protocol-agnostic layers. Each protocol (TR-069, MQTT, USP) becomes a thin adapter over a shared pipeline.
5.2.3. Changes
| Change | Details |
|---|---|
Define |
Protocol-agnostic session with methods: |
Extract |
Consolidate |
Extract |
Consolidate |
Extract |
Consolidate |
Thin adapters |
TR-069: Parse CWMP SOAP → |
5.2.4. Impact Assessment
| Area | Impact | Description | Risk |
|---|---|---|---|
Codebase |
HIGH |
Refactors 3 protocol modules + |
MEDIUM — behavioral changes in hot path |
Runtime behavior |
LOW |
Same logic, different call path. Must verify no regression in device session flows. |
MEDIUM — integration risk |
Build pipeline |
LOW |
New module or package within existing module |
LOW |
Database |
NONE |
No schema changes |
NONE |
Deployment |
NONE |
Still single WAR |
NONE |
5.2.5. Test Coverage Plan
| Test Type | What to Cover | Tools |
|---|---|---|
Unit tests per pipeline stage |
Each handler tested in isolation with mocked dependencies. Test |
JUnit 5 + Mockito |
Protocol adapter tests |
Verify each adapter correctly translates protocol-specific messages to |
JUnit 5 + mock protocol messages |
End-to-end device session tests |
Full pipeline execution for each protocol: bootstrap → inform → profile → tasks → persist → finalize. |
Integration tests with embedded database (H2) + mock Hazelcast |
Regression: TR-069 flows |
All existing TR-069 scenarios: first inform, periodic inform, connection request, firmware download, value change. |
TR-069 emulator + integration test |
Regression: MQTT flows |
Device registration, parameter reporting, task execution via MQTT. |
MQTT emulator + integration test |
Regression: USP flows |
USP registration, CRUD operations, subscription notifications. |
USP emulator + integration test |
Performance baseline |
Measure inform processing time before/after refactoring. Must not regress. |
JMH microbenchmarks on |
// DeviceSession contract test @Test void tr069AdapterImplementsDeviceSession() @Test void mqttAdapterImplementsDeviceSession() @Test void uspAdapterImplementsDeviceSession() // Pipeline unit tests @Test void bootstrapHandler_newDevice_createsEntity() @Test void bootstrapHandler_existingDevice_updatesLastConnect() @Test void informProcessor_detectsParameterChanges() @Test void profileApplicator_firstConnect_appliesProfile() @Test void profileApplicator_subsequentConnect_skips() @Test void taskOrchestrator_pendingTasks_executesInOrder() @Test void taskOrchestrator_noTasks_completesGracefully() @Test void parameterPersistor_bulkSave_firesChangeEvents() // End-to-end per protocol @Test void tr069_fullBootstrapToFinalize() @Test void mqtt_fullBootstrapToFinalize() @Test void usp_fullBootstrapToFinalize() // Performance regression @Test void informProcessingTime_doesNotRegress()
5.3. Phase 3: SOAP to REST API Modernization
5.3.1. Objective
Decompose the 162-method SOAP god object into domain-specific REST controllers with OpenAPI documentation. Replace internal SOAP-based node-to-node communication.
5.3.3. SOAP Method Decomposition
| REST Controller | Methods from SOAP | Estimated Endpoints |
|---|---|---|
|
getCpeList, getCpeInfo, deleteCpe, getCpeBySerial, getCpeStatus, … |
~25 |
|
getParameterValues, setParameterValues, getParameterNames, getParameterAttributes, … |
~15 |
|
getEventMonitors, createEventMonitor, deleteEventMonitor, getHardcodedEvents, … |
~12 |
|
getProfiles, createProfile, updateProfile, deleteProfile, getProfileConditions, … |
~15 |
|
getUpdateGroups, createUpdateGroup, activateUpdateGroup, getUpdateGroupStatus, … |
~10 |
|
getQoeMonitoringGroups, startQoeMonitoring, stopQoeMonitoring, getQoeData, … |
~12 |
|
getBlacklist, addToBlacklist, getWhitelist, addToWhitelist, … |
~8 |
|
addPendingTask, getPendingTasks, getCompletedTasks, downloadFile, … |
~15 |
|
runIpPing, runTraceroute, runDownloadDiag, runUploadDiag, … |
~10 |
|
getConfigParameters, setConfigParameter, configureAccountInfoMapping, … |
~8 |
5.3.4. Impact Assessment
| Area | Impact | Description | Risk |
|---|---|---|---|
Codebase |
HIGH |
~10 new REST controllers, DTO mapping layer, OpenAPI annotations. SOAP classes kept but deprecated. |
LOW — additive change, SOAP still works |
API consumers |
HIGH |
External clients must eventually migrate from SOAP to REST. Dual-run period required. |
MEDIUM — client coordination needed |
Internal communication |
MEDIUM |
|
MEDIUM |
Database |
NONE |
No schema changes |
NONE |
Deployment |
LOW |
Additional REST endpoints exposed. May need reverse proxy config update. |
LOW |
5.3.5. Test Coverage Plan
| Test Type | What to Cover | Tools |
|---|---|---|
REST controller unit tests |
Each endpoint: valid request → expected response. Error cases: invalid input, missing auth, not found. |
JUnit 5 + MockMvc ( |
SOAP ↔ REST parity tests |
For every migrated operation: call both SOAP and REST with identical input, assert identical output. This is the critical safety net. |
Parameterized tests with both clients |
OpenAPI contract tests |
Validate that generated OpenAPI spec matches expected schema. Validate request/response examples. |
Swagger Parser + JSON Schema validation |
API versioning tests |
Verify |
Integration tests |
Authentication tests |
REST endpoints enforce same auth as SOAP. Test token/session propagation. |
Spring Security test support ( |
Load tests |
REST vs SOAP performance comparison. REST should be faster (no XML overhead). |
JMeter or Gatling — same scenarios, both endpoints |
// Parity tests (most critical)
@ParameterizedTest
@MethodSource("allMigratedOperations")
void soapAndRestReturnIdenticalResults(Operation op, Object input)
// REST controller tests
@WebMvcTest(DeviceController.class)
@Test void getDeviceList_returnsPagedResult()
@Test void getDeviceById_notFound_returns404()
@Test void deleteDevice_unauthorized_returns401()
// OpenAPI validation
@Test void openApiSpec_isValid()
@Test void openApiSpec_coversAllEndpoints()
// Performance comparison
@Test void restEndpoint_fasterThanSoap_forLargePayloads()
5.4. Phase 4: Kafka for Event Bus & Inter-Node Communication
5.4.1. Objective
Replace the in-process DefaultAsyncEventBusImpl and SOAP-based SoapNodeNotifier with Apache Kafka.
Events become durable, replayable, and consumable by external services.
5.4.3. Event Topic Mapping
| Kafka Topic | Event Classes | Partition Key | Volume |
|---|---|---|---|
|
|
|
Medium |
|
|
|
HIGH — fired on every inform |
|
|
|
Medium |
|
Event monitor trigger results, hardcoded event matches |
|
Low-Medium |
|
|
|
Low |
|
|
|
Low |
|
|
|
Very Low |
|
|
|
Low |
5.4.4. Impact Assessment
| Area | Impact | Description | Risk |
|---|---|---|---|
Infrastructure |
HIGH |
New dependency: Apache Kafka cluster (3+ brokers for production). New operational concern. |
MEDIUM — Kafka is well-understood but adds ops complexity |
Codebase |
MEDIUM |
New |
MEDIUM — serialization edge cases |
Runtime behavior |
MEDIUM |
Events become eventually-consistent (Kafka latency ~1-5ms). Previously in-process was near-instant. |
LOW — acceptable for all current use cases |
Ordering guarantees |
LOW |
Kafka guarantees order within a partition. Partition by |
LOW |
Node-to-node communication |
HIGH |
|
MEDIUM — must verify all distributed event flows |
Hazelcast scope reduction |
MEDIUM |
5 Hazelcast Topics ( |
LOW |
5.4.5. Test Coverage Plan
| Test Type | What to Cover | Tools |
|---|---|---|
Event serialization/deserialization |
Every event class round-trips through Kafka serialization without data loss. Test all 35+ event types. |
JUnit 5 + Kafka Avro/JSON serializer |
Event ordering tests |
Events for the same |
Embedded Kafka ( |
Consumer group tests |
Multiple ACS nodes in the same consumer group process events exactly once. New node joining rebalances correctly. |
Embedded Kafka + multiple consumer instances |
Event loss tests |
Kill a node mid-publish. Verify uncommitted events are replayed on restart (at-least-once). |
Integration test with simulated crashes |
Latency benchmarks |
Measure event publish-to-consume latency. Must be < 10ms for p99 within same datacenter. |
Kafka metrics + custom latency tracking |
Distributed event tests |
Events previously handled by |
Multi-instance integration test |
Backward compatibility |
During migration: local bus and Kafka run in parallel. Same event is handled by both. No duplicate processing. |
Feature-flag test: toggle between local/kafka/dual |
Load test |
Sustained 10k events/second (simulating 10k device informs). Kafka keeps up, no consumer lag. |
Kafka producer load test + consumer lag monitoring |
// Serialization round-trip (parameterized for all events)
@ParameterizedTest
@MethodSource("allEventClasses")
void eventSurvivesKafkaSerialization(Class<? extends Event> eventClass)
// Ordering
@Test void eventsForSameDevice_consumedInOrder()
// Exactly-once within consumer group
@Test void twoConsumersInGroup_eachEventProcessedOnce()
// Crash resilience
@Test void nodeKilledDuringPublish_eventsReplayedOnRestart()
// Latency
@Test void publishToConsume_p99Under10ms()
// Migration safety
@Test void dualMode_localAndKafka_noDoubleProcessing()
5.5. Phase 5: Redis Replacing Hazelcast
5.5.1. Objective
Replace all 80+ Hazelcast IMap caches with Redis. Replace distributed locks with Redisson. Remove Hazelcast dependency entirely.
5.5.3. Cache Migration Strategy
Migrate in batches by risk level:
| Batch | Caches | Rationale | Risk |
|---|---|---|---|
Batch 1 (Low risk) |
|
Low write frequency, easy to validate, used across many modules — good canary. |
LOW |
Batch 2 (Medium risk) |
|
Read-heavy, updated on profile changes. Well-bounded domain. |
LOW |
Batch 3 (Medium risk) |
|
Security-critical but low-frequency updates. |
MEDIUM |
Batch 4 (Higher risk) |
|
These are on the inform hot path with near-cache. Must benchmark Redis performance. |
MEDIUM |
Batch 5 (Higher risk) |
|
Core device lookup. Must handle high concurrency. |
MEDIUM |
Batch 6 (Highest risk) |
|
Locks are correctness-critical. Redisson’s |
HIGH |
Batch 7 (Final) |
All remaining caches (event monitoring, QoE, task coordination, encryption) + |
Cleanup. Replace executor with local thread pool + Kafka for distributed dispatch. |
MEDIUM |
5.5.4. Impact Assessment
| Area | Impact | Description | Risk |
|---|---|---|---|
Infrastructure |
HIGH |
New dependency: Redis Cluster (3+ nodes). Remove: Hazelcast cluster. Net operational change is a wash. |
MEDIUM — different ops model |
Codebase |
HIGH |
Cache interface implementations change from Hazelcast IMap to Redis operations. ~80 cache sites. |
MEDIUM — large change surface but mechanical |
Performance |
MEDIUM |
Near-cache (local L1) lost — Redis is always network hop. May need local Caffeine L1 cache for hot maps. |
MEDIUM — must benchmark |
Distributed locks |
HIGH |
Redisson RLock vs Hazelcast IMap lock. Different failure modes, different lease policies. |
HIGH — correctness critical |
User Code Deployment |
REMOVED |
No more deploying |
LOW — simplification |
Serialization |
MEDIUM |
Hazelcast relied on Java serialization + deployed classes. Redis uses JSON/Protobuf — must define serialization for all cached types. |
MEDIUM |
5.5.5. Test Coverage Plan
| Test Type | What to Cover | Tools |
|---|---|---|
Cache operation tests |
For each migrated cache: put/get/evict/ttl behave identically to Hazelcast. Test all 80+ caches. |
JUnit 5 + Embedded Redis (Testcontainers) |
Lock correctness tests |
Two threads/processes competing for the same lock: only one wins. Lock auto-expires on holder crash. Reentrant lock works. |
Testcontainers Redis + concurrent test threads |
Near-cache replacement benchmarks |
Hot path caches ( |
JMH microbenchmarks |
Serialization compatibility |
All cached entity types serialize/deserialize correctly with the chosen Redis serializer. |
Parameterized tests per entity type |
Failover tests |
Redis master failure: Sentinel promotes replica. Application reconnects. No data loss for persistent keys. |
Testcontainers with Sentinel + simulated failure |
Performance regression |
End-to-end inform processing time. Must not regress > 5% vs Hazelcast. |
Load test: 5k concurrent informs, measure p95 latency |
Gradual migration tests |
During migration: some caches on Hazelcast, some on Redis. System works correctly with mixed backends. |
Integration test with feature flags per cache batch |
// Per-cache correctness (parameterized)
@ParameterizedTest
@MethodSource("allCacheNames")
void cacheOperations_identicalToHazelcast(String cacheName)
// Lock correctness
@Test void distributedLock_mutualExclusion()
@Test void distributedLock_autoExpireOnCrash()
@Test void distributedLock_reentrant()
// Performance
@Test void parameterCacheLookup_p95Under2ms()
@Test void informProcessing_noRegressionVsHazelcast()
// Failover
@Test void redisMasterFailure_applicationRecovers()
5.6. Phase 6: Service Extraction
5.6.1. Objective
Extract Events, Update Group, and QoE as independent services. Each gets its own deployment, scaling, and performance measurement.
5.6.3. Service Boundaries
| Service | Owns (tables) | Consumes (Kafka) | Produces (Kafka) |
|---|---|---|---|
Core ACS |
|
|
|
Event Service |
|
|
|
Update Group Service |
|
|
|
QoE Service |
|
|
|
5.6.4. Impact Assessment
| Area | Impact | Description | Risk |
|---|---|---|---|
Architecture |
CRITICAL |
Three new independent services. Service discovery, API gateway, distributed tracing needed. |
HIGH — distributed system complexity |
Database |
HIGH |
Table ownership split. May need schema separation or cross-service queries via API. |
HIGH — data ownership boundaries |
Deployment |
HIGH |
3 new deployable units. CI/CD pipelines, Docker images, health checks, monitoring per service. |
MEDIUM — operational complexity |
Performance |
POSITIVE |
Per-service scaling. Can scale Event Service independently during high-event periods. |
LOW — this is the goal |
Team organization |
MEDIUM |
Teams can now own individual services. Clear ownership boundaries. |
LOW — organizational benefit |
Observability |
HIGH |
Need distributed tracing (Jaeger/Zipkin), centralized logging, per-service dashboards. |
MEDIUM — must be in place before extraction |
5.6.5. Test Coverage Plan
| Test Type | What to Cover | Tools |
|---|---|---|
Service contract tests |
Each service’s Kafka consumers/producers agree on schema. REST API contracts between services are verified. |
Pact (consumer-driven contract testing) or Spring Cloud Contract |
Service integration tests |
Full flow: device informs → Core ACS publishes event → Event Service processes and sends notification. Verify end-to-end. |
Docker Compose with all services + Kafka + Redis + DB |
Service isolation tests |
Each service starts independently. Each service handles Kafka unavailability gracefully (circuit breaker). Each service handles Redis unavailability gracefully. |
Testcontainers with selective infrastructure |
Data consistency tests |
Events published by Core ACS are consumed exactly once by Event Service. No events lost during service restart. |
Chaos testing: restart services during load |
Performance per service |
Establish per-service performance baselines. This is the performance formula enabler. |
Per-service load tests + Grafana dashboards |
Failover tests |
One service goes down — others continue functioning. Kafka buffers events until service recovers. No data loss. |
Chaos monkey-style testing |
End-to-end regression |
All existing functional test scenarios pass with the distributed architecture. |
Full regression suite against Docker Compose environment |
// Contract tests @Test void coreACS_publishesDeviceLifecycleEvent_matchingEventServiceSchema() @Test void eventService_consumesParameterChangedEvent_matchingCoreSchema() @Test void updateGroupService_producesTaskCommand_matchingCoreSchema() // Isolation tests @Test void eventService_startsWithoutCoreACS() @Test void eventService_handlesKafkaUnavailability_withCircuitBreaker() @Test void updateGroupService_handlesRedisUnavailability_withFallback() // Performance formula tests @Test void coreACS_2instances_handles10kInformsPerMinute() @Test void eventService_1instance_handles5kEventsPerMinute() @Test void updateGroupService_1instance_handles1kUpdatesPerMinute() // Chaos tests @Test void eventServiceRestart_noEventsLost() @Test void coreACSRestart_kafkaBuffersEvents_eventServiceCatchesUp()
5.7. Phase 7: ClickHouse Expansion for Historical Data
5.7.1. Objective
Migrate high-volume historical tables from MySQL/Oracle to ClickHouse. Leverage existing ClickHouse integration (QoE) and extend to device logs, parameter changes, and completed tasks.
5.7.3. Migration Plan per Table
| Table | Est. Rows/Day | Current Size | ClickHouse Engine | Priority |
|---|---|---|---|---|
|
~500k-5M |
Largest table |
|
1 (highest) |
|
~100k-1M |
Very large |
|
2 |
|
~50k-500k |
Large |
|
3 |
|
~10k-100k |
Medium-Large |
|
4 |
|
~10k-50k |
Medium |
|
5 |
|
~1k-10k |
Small-Medium |
|
6 |
|
~1k-5k |
Small-Medium |
|
7 |
5.7.4. Impact Assessment
| Area | Impact | Description | Risk |
|---|---|---|---|
Query performance |
HIGH (positive) |
Analytical queries (aggregations, time-range filters) 10-100x faster. Device history pages load instantly. |
LOW — performance improvement |
Write performance |
MEDIUM |
Dual-write during transition adds latency. Kafka-based async write to ClickHouse mitigates this. |
LOW — async via Kafka |
Storage |
HIGH (positive) |
ClickHouse columnar compression reduces storage 5-10x. MySQL freed from large tables. |
LOW — cost reduction |
Application code |
MEDIUM |
DAO implementations switch datasource. Existing |
LOW — pattern exists |
Reporting |
HIGH (positive) |
Grafana dashboards can query ClickHouse directly. New analytical capabilities. |
LOW — additive |
Data migration |
MEDIUM |
Historical data must be bulk-loaded into ClickHouse. May take hours for large datasets. |
MEDIUM — one-time operation |
5.7.5. Test Coverage Plan
| Test Type | What to Cover | Tools |
|---|---|---|
Dual-write consistency |
Same record appears in both MySQL and ClickHouse during transition. No data loss. |
Integration test comparing row counts + sampling |
Query parity tests |
ClickHouse queries return same results as MySQL for identical time ranges and filters. |
Parameterized tests: same query → both DBs → compare results |
ClickHouse-specific query tests |
Aggregation queries, time-series rollups, partition pruning work correctly. |
JUnit + ClickHouse JDBC |
Bulk migration test |
Historical data migration completes without errors. Row counts match. |
Migration script + validation query |
Performance benchmarks |
Measure query times for: last 24h device logs, parameter change history, completed task aggregation. Must be 10x+ faster than MySQL. |
JMH or dedicated benchmark suite |
Retention policy tests |
ClickHouse TTL (if configured) correctly drops old partitions. No premature data loss. |
Time-accelerated test with TTL |
// Dual-write consistency
@Test void dualWrite_recordAppearsInBothMysqlAndClickHouse()
@Test void dualWrite_kafkaConsumerLag_doesNotExceed1000()
// Query parity
@ParameterizedTest
@MethodSource("historicalQueries")
void queryReturnsIdenticalResults_mysqlVsClickHouse(String query)
// Performance
@Test void deviceLogQuery_last24h_under100ms_inClickHouse()
@Test void parameterChangeAggregation_lastMonth_under500ms()
// Migration
@Test void bulkMigration_allRowsCopied_noDataLoss()
6. Cross-Phase Concerns
6.2. Observability Requirements per Phase
| Phase | Monitoring Additions | Dashboards |
|---|---|---|
Phase 1-3 |
None — existing monitoring sufficient |
Existing Grafana dashboards |
Phase 4 |
Kafka broker metrics, consumer lag, topic throughput, event processing latency |
New: Kafka dashboard, Event Bus dashboard |
Phase 5 |
Redis memory usage, hit/miss ratios, lock contention, Redisson metrics |
New: Redis dashboard. Update: replace Hazelcast dashboard |
Phase 6 |
Per-service CPU/memory/GC, inter-service latency, circuit breaker states, distributed traces |
New: per-service dashboards, service mesh dashboard, distributed trace viewer |
Phase 7 |
ClickHouse query performance, insert rates, partition sizes, replication lag |
New: ClickHouse dashboard, historical data dashboard |
6.3. Performance Formula (Phase 6 Goal)
Once services are extracted, capacity can be modeled per-service:
Total system capacity = min( core_acs_capacity(N_core, scenario), event_service_capacity(N_event, event_rate), update_group_capacity(N_ug, group_size), qoe_service_capacity(N_qoe, monitoring_rate) ) Where: core_acs_capacity = N_core × informs_per_instance_per_minute event_service_capacity = N_event × events_per_instance_per_minute update_group_capacity = N_ug × devices_per_instance_per_minute qoe_service_capacity = N_qoe × metrics_per_instance_per_minute
Each variable is independently measurable and scalable. Bottleneck identification becomes trivial.
6.4. Skill Requirements
| Phase | Team Skills Needed |
|---|---|
Phase 1 |
Java, Maven multi-module projects, interface design |
Phase 2 |
Design patterns (Strategy, Template Method), protocol knowledge (TR-069/MQTT/USP) |
Phase 3 |
Spring MVC, REST API design, OpenAPI, HTTP security |
Phase 4 |
Apache Kafka (producers, consumers, schemas, operations), event-driven architecture |
Phase 5 |
Redis (data structures, clustering, Sentinel), Redisson, cache strategies (L1/L2) |
Phase 6 |
Microservices patterns (service discovery, circuit breakers, distributed tracing), Docker, Kubernetes (optional), Pact/contract testing |
Phase 7 |
ClickHouse (MergeTree engines, partitioning, SQL dialect), data migration tooling |
7. Risk Matrix
| Risk | Phase | Probability | Impact | Mitigation |
|---|---|---|---|---|
Module extraction breaks runtime wiring |
1 |
Medium |
Low |
Zero behavioral change — compile-only. |
Device flow refactoring introduces session bugs |
2 |
Medium |
High |
Feature flag to toggle old/new path. Extensive protocol emulator tests. Run both paths in shadow mode before cutover. |
SOAP client migration takes longer than expected |
3 |
High |
Medium |
Dual-run indefinitely. No deadline to remove SOAP — it just costs maintenance. |
Kafka adds latency to event processing |
4 |
Low |
Medium |
Kafka within same DC is ~1-5ms. If needed, keep critical path events local and only replicate to Kafka asynchronously. |
Redis lock semantics differ from Hazelcast |
5 |
Medium |
High |
Extensive lock correctness tests. Use Redisson (battle-tested). Migrate locks last (Batch 6) after all caches are proven. |
Near-cache loss degrades hot path |
5 |
Medium |
Medium |
Add local Caffeine L1 cache with short TTL (5-30s) in front of Redis for |
Distributed system complexity (cascading failures) |
6 |
Medium |
High |
Circuit breakers (Resilience4j), bulkheads, retry with backoff. Kafka as buffer prevents cascading. Each service must function degraded when dependencies are down. |
Data inconsistency between services |
6 |
Medium |
High |
Event sourcing via Kafka ensures eventual consistency. Consumer idempotency is mandatory. Schema registry prevents breaking changes. |
ClickHouse query compatibility |
7 |
Low |
Low |
SQL dialect differences are small. Existing |
Team skill gaps |
4-6 |
Medium |
Medium |
Training sprints before each infrastructure phase. Proof of concept in non-production first. |
8. Decision Log
Track key architectural decisions made during the evolution.
| ID | Phase | Decision | Rationale |
|---|---|---|---|
ADR-001 |
1 |
Use Java interfaces (not REST) for inter-module contracts within the monolith |
Modules still run in the same JVM during Phases 1-5. REST overhead is unnecessary until Phase 6 extraction. |
ADR-002 |
4 |
Use Kafka (not RabbitMQ) for event bus |
Kafka provides durable, replayable, partitioned event log. Needed for event sourcing and audit trail. Higher throughput for parameter change events (~5M/day). |
ADR-003 |
5 |
Use Redis (not Memcached) for cache replacement |
Redis supports distributed locks (Redisson), pub/sub (backup), and data structures (Hash, Set). Memcached is cache-only. |
ADR-004 |
5 |
Add Caffeine L1 cache for hot Redis maps |
Near-cache loss from Hazelcast migration. Caffeine provides <1ms local reads with configurable TTL. Redis is L2 for cross-node consistency. |
ADR-005 |
7 |
Use Kafka → ClickHouse pipeline (not direct dual-write) |
Decouples write path. Application writes to Kafka; a dedicated consumer batch-inserts to ClickHouse. Better throughput, back-pressure handling, and retry. |
9. Phase Completion Checklist
Use this checklist to verify each phase is complete before starting the next.
9.1. Phase 1 Checklist
-
shared-modelsmodule published and used by all dependent modules -
*-apiinterface JARs exist for: events, update-group, qoe, provision, core -
No circular Maven dependencies (enforcer plugin passes)
-
@SpringBootTestapplication context loads successfully -
All existing tests pass
-
Maven dependency tree documented
9.2. Phase 2 Checklist
-
DeviceSessioninterface defined with TR-069, MQTT, USP implementations -
Pipeline handlers: Bootstrap, Inform, Profile, Task, ParameterPersist, Finalizer
-
All three protocols pass end-to-end tests through new pipeline
-
Performance benchmark shows no regression
-
Feature flag allows fallback to old flow
9.3. Phase 3 Checklist
-
All 162 SOAP methods have REST equivalents
-
SOAP ↔ REST parity tests pass for 100% of endpoints
-
OpenAPI 3.0 spec generated and validated
-
BroadcastingWebService(internal SOAP) replaced or prepared for Phase 4 -
API versioning (
/api/v1/) in place -
Authentication works on REST endpoints
9.4. Phase 4 Checklist
-
Kafka cluster deployed and operational
-
All 35+ event types have Kafka topic mappings
-
KafkaEventBusAdapterreplacesDefaultAsyncEventBusImpl -
Hazelcast Topics (5) removed, replaced by Kafka topics
-
SoapNodeNotifierremoved -
Event ordering tests pass (per-device ordering)
-
Event loss test passes (at-least-once delivery)
-
Consumer lag monitoring in Grafana
9.5. Phase 5 Checklist
-
All 80+ Hazelcast IMap caches migrated to Redis
-
All distributed locks migrated to Redisson
-
HazelcastCacheFactoryremoved -
Hazelcast dependency removed from POM
-
Caffeine L1 cache added for hot maps
-
Lock correctness tests pass
-
Performance regression test passes (< 5% degradation)
-
Redis monitoring dashboard in Grafana
9.6. Phase 6 Checklist
-
Event Service deployed as independent application
-
Update Group Service deployed as independent application
-
QoE Service deployed as independent application
-
Contract tests pass between all services
-
Each service has independent CI/CD pipeline
-
Distributed tracing operational (Jaeger/Zipkin)
-
Per-service Grafana dashboards
-
Performance formula established and validated
-
Chaos testing passed (service failure resilience)
9.7. Phase 7 Checklist
-
cpe_changed_parameterin ClickHouse with dual-write -
cpe_login ClickHouse with dual-write -
cpe_completed_taskin ClickHouse with dual-write -
cpe_trace_login ClickHouse with dual-write -
Query parity tests pass (MySQL vs ClickHouse)
-
Performance benchmarks show 10x+ improvement for analytical queries
-
Retention/TTL policies configured
-
Grafana dashboards updated to query ClickHouse
10. Appendix A: Current Hazelcast Cache Inventory
| Cache Name | Module | Access Pattern | Migration Batch (Phase 5) |
|---|---|---|---|
|
ftdm-cache |
Read-heavy |
Batch 1 |
|
ftdm-cache |
Read-heavy |
Batch 1 |
|
ftdm-cache |
Read-heavy |
Batch 1 |
|
ftdm-profile |
Read-heavy |
Batch 2 |
|
ftdm-profile |
Read-heavy |
Batch 2 |
|
ftdm-security |
Read-heavy |
Batch 3 |
|
ftdm-security |
Read-heavy |
Batch 3 |
|
ftdm-cache |
Read-heavy, near-cache |
Batch 4 |
|
ftdm-cache |
Read-heavy, near-cache |
Batch 4 |
|
ftdm-cache |
Read-heavy |
Batch 5 |
|
ftdm-cache |
Read/Write |
Batch 5 |
|
ftdm-core |
Lock |
Batch 6 |
|
ftdm-core, ftdm-qoe |
Lock |
Batch 6 |
|
ftdm-events |
Read-heavy |
Batch 7 |
|
ftdm-qoe |
Read/Write |
Batch 7 |
|
ftdm-core |
Read/Write |
Batch 7 |
|
ftdm-provision |
Read/Write |
Batch 7 |
11. Appendix B: Event Type Inventory
| Event Class | Published By | Kafka Topic (Phase 4) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Task handlers |
|
|
|
|
|
Security module |
|
|
Protocol modules |
|
|
Protocol modules |
|
|
Admin operations |
|
|
Provisioning |
|