# Microservices Refactoring Overview

This document provides an extended analysis of the four microservices (`server`, `location`, `bridge`, `decoder`), their current structure, and a concrete refactoring plan for better reusability, separation of concerns, and maintainability.

---

## 1. Extended Overview of Each Service

### 1.1 `cmd/server/main.go` (~211 lines)

**Role:** HTTP API + Kafka consumers + event loop. Central API for gateways, zones, trackers, parser configs, settings, and tracks; consumes location events and alert beacons; runs a ticker to publish tracker list to MQTT topic.

**What lives in `main()` today:**

| Section | Lines (approx) | Responsibility |
|--------|-----------------|----------------|
| Bootstrap | 36–46 | Load config, create AppState, init Kafka manager, create logger, signal context |
| DB + CORS | 48–55 | Connect DB, build CORS options |
| Kafka writers | 56–59 | Populate writers: apibeacons, alert, mqtt, settings, parser |
| Config load & DB seed | 60–89 | Open config file, unmarshal JSON, create configs in DB, find configs, send each to parser topic, call `UpdateDB` |
| Kafka readers | 96–105 | Populate readers: locevents, alertbeacons; create channels; start 2 consumer goroutines |
| Router setup | 107–136 | Mux router + ~25 route registrations (handlers get `db`, `writer`, `ctx` directly) |
| HTTP server | 138–150 | CORS wrapper, handler, `http.Server`, `ListenAndServe` in goroutine |
| **Event loop** | 154–191 | `select`: ctx.Done, chLoc → `LocationToBeaconService`, chEvents → update tracker battery/temp in DB, beaconTicker → marshal trackers, write to mqtt topic |
| Shutdown | 193–210 | Server shutdown, wait group, clean Kafka, cleanup logger |

**Pain points:**

- **Heavy main:** Config loading, DB seeding, and “sync parser configs to Kafka” are one-off startup logic mixed with wiring.
- **Event loop in main:** Business logic (location→beacon, decoder event→DB update, ticker→mqtt) lives in `main` instead of a dedicated component.
- **Handlers take 3–4 args:** `db`, `*kafka.Writer`, `context.Context` passed into every handler; no shared “server” or “app” struct.
- **Global `wg`:** Package-level `sync.WaitGroup` instead of a scoped lifecycle object.

---

### 1.2 `cmd/location/main.go` (~246 lines)

**Role:** Location algorithm service. Consumes raw beacons and settings from Kafka; on a ticker runs either “filter” (score-based) or “ai” (HTTP inference) and writes location events to Kafka.

**What lives in `main()` and adjacent:**

| Section | Lines (approx) | Responsibility |
|--------|-----------------|----------------|
| Bootstrap | 26–38 | AppState, config, Kafka manager, logger, signal context |
| Kafka | 39–54 | Readers: rawbeacons, settings; writer: locevents; channels; 2 consumer goroutines |
| **Event loop** | 56–90 | ctx.Done, locTicker (get settings, branch filter vs ai), chRaw → assignBeaconToList, chSettings → UpdateSettings |
| Shutdown | 92–100 | Break, wg.Wait, clean Kafka, cleanup |

**Logic outside main but still in this package:**

- `getAI` (102–122): HTTP client, TLS skip verify, get token, infer position (API calls).
- `getLikelyLocations` (124–203): Full “filter” algorithm: iterate beacons, score by RSSI/seen, confidence, write `HTTPLocation` to Kafka. ~80 lines.
- `assignBeaconToList` (205–244): Append metric to beacon in AppState, sliding window.

**Pain points:**

- **Algorithm in cmd:** `getLikelyLocations` and `assignBeaconToList` are core domain logic but live under `cmd/location`; not reusable or testable in isolation.
- **getAI in main pkg:** HTTP and TLS setup and API calls are in `main`; should be behind an interface (e.g. “LocationInference”) for testing and reuse.
- **Magic numbers:** `999`, `1.5`, `0.75`, etc. in `getLikelyLocations`; should be config or named constants.
- **Duplicate bootstrap:** Same Kafka/logger/context pattern as server and bridge.

---

### 1.3 `cmd/bridge/main.go` (~212 lines)

**Role:** MQTT ↔ Kafka bridge. Subscribes to MQTT; converts messages to Kafka (rawbeacons); consumes apibeacons, alert, mqtt from Kafka and publishes to MQTT.

**What lives in `main()` and adjacent:**

| Section | Lines (approx) | Responsibility |
|--------|-----------------|----------------|
| Bootstrap | 99–118 | AppState, config, Kafka, logger, context |
| Kafka | 112–127 | Readers: apibeacons, alert, mqtt; writer: rawbeacons; channels; 3 consumer goroutines |
| MQTT client | 129–150 | Options, client ID, handlers, connect, `sub(client)` |
| **Event loop** | 152–188 | ctx.Done, chApi (POST/DELETE → lookup), chAlert → Publish /alerts, chMqtt → Publish /trackers |
| Shutdown | 190–203 | Break, wg.Wait, Kafka cleanup, MQTT disconnect, cleanup |

**Logic in package:**

- `mqtthandler` (27–84): Parse JSON array of `RawReading` or CSV; for JSON, map MAC→ID via AppState, build `BeaconAdvertisement`, write to Kafka. CSV branch does nothing useful after parse (dead code).
- `messagePubHandler`, `connectHandler`, `connectLostHandler`: MQTT callbacks.
- `sub(client)`: Subscribe to `publish_out/#`.

**Pain points:**

- **MQTT and Kafka logic in cmd:** `mqtthandler` is central to the bridge but lives in `main.go`; hard to unit test and reuse.
- **Handler signature:** `mqtthandler(writer, topic, message, appState)` and package-level `messagePubHandler` close over `writer` and `appState`; no injectable “BridgeHandler” or service.
- **Topic parsing:** `strings.Split(topic, "/")[1]` can panic if topic format changes.
- **Dead CSV branch:** Parses CSV but never produces Kafka messages; either implement or remove.

---

### 1.4 `cmd/decoder/main.go` (~139 lines)

**Role:** Decode raw beacon payloads using a parser registry; consume rawbeacons and parser config updates; produce alertbeacons.

**What lives in `main()` and adjacent:**

| Section | Lines (approx) | Responsibility |
|--------|-----------------|----------------|
| Bootstrap | 25–55 | AppState, config, parser registry, logger, context, Kafka readers/writers, channels, 2 consumers |
| **Event loop** | 57–76 | ctx.Done, chRaw → processIncoming (decodeBeacon), chParser → add/delete/update registry |
| Shutdown | 78–86 | Break, wg.Wait, Kafka cleanup, cleanup |

**Logic in package:**

- `processIncoming` (88–95): Wraps `decodeBeacon`, logs errors.
- `decodeBeacon` (97–138): Hex decode, remove flags, parse AD structures, run parser registry, dedupe by event hash, write to alertbeacons. This is core decoder logic.

**Pain points:**

- **Decode logic in cmd:** `decodeBeacon` belongs in a `decoder` or `parser` service/package under `internal`, not in `cmd`.
- **Parser registry in main:** Registry is created and updated in main; could be a component that main wires and passes into a “DecoderService” or “EventProcessor”.

---

## 2. Cross-Cutting Observations

### 2.1 Duplication Across All Four `main.go`

- **Bootstrap:** Each service does: load service-specific config, create `AppState` (or not, server doesn’t use it for the same purpose), init `KafkaManager`, create logger, `signal.NotifyContext`.
- **Kafka pattern:** `PopulateKafkaManager` for readers/writers, create channels, `wg.Add(N)`, `go Consume(...)`.
- **Shutdown:** Break loop, `wg.Wait()`, `CleanKafkaReaders/Writers`, optional MQTT disconnect, logger cleanup.

This suggests a small **runtime/bootstrap** package that returns config, logger, Kafka manager, and context (and optionally an “App” struct that owns lifecycle).

### 2.2 Where Business Logic Lives

- **Server:** Event loop in main (location→beacon, decoder event→DB, ticker→mqtt); handlers in `internal/pkg/controller` but take raw `*gorm.DB` and `*kafka.Writer`.
- **Location:** Filter algorithm and “assign beacon to list” in `cmd/location`; no `internal` location or algorithm package.
- **Bridge:** MQTT message handling in `cmd/bridge`; no `internal` bridge or mqtt handler package.
- **Decoder:** Decode and registry handling in `cmd/decoder`; parser registry is in `model`, but “process raw → alert” is in main.

So today, a lot of “service logic” is either in `main` or in `cmd/<service>` instead of in `internal` behind clear interfaces.

### 2.3 Dependency Direction Today

- All `cmd/*` import from `internal/pkg/*` (config, logger, kafkaclient, model, service, controller, database, apiclient, appcontext).
- Controllers and services take concrete types (`*gorm.DB`, `*kafka.Writer`). No interfaces for “store” or “message writer,” so testing and swapping implementations require mocks at the concrete type level.
- `model` is a single large namespace (beacons, parser, trackers, gateways, zones, settings, etc.); no split by bounded context (e.g. beacon vs parser vs location).

---

## 3. Proposed Directory and Package Layout

Goal: keep `cmd/<service>/main.go` as a **thin composition layer** that only wires config, infra, and “app” components, and runs the process. All reusable logic and “where things live” should be clear from the directory structure.

### 3.1 Recommended Tree

```text
internal/
├── pkg/
│   ├── config/              # Keep; optional: split LoadServer/LoadLocation into configs subpackage or env schema
│   ├── logger/              # Keep
│   │
│   ├── domain/              # NEW: shared domain types and interfaces (no infra)
│   │   ├── beacon.go        # Beacon, BeaconEvent, BeaconMetric, BeaconAdvertisement, BeaconsList, etc.
│   │   ├── parser.go        # Config, KafkaParser, BeaconParser, ParserRegistry (or keep registry in service)
│   │   ├── location.go      # HTTPLocation, location scoring constants
│   │   ├── trackers.go      # Tracker, ApiUpdate, Alert, etc.
│   │   └── types.go         # RawReading, Settings, and other shared DTOs
│   │
│   ├── store/               # NEW: in-memory / app state (optional rename of appcontext)
│   │   └── appstate.go      # AppState (move from common/appcontext), same API
│   │
│   ├── messaging/           # NEW: Kafka (and optionally MQTT) behind interfaces
│   │   ├── kafka.go         # Manager, Consume, Writer/Reader interfaces, implementation
│   │   └── interfaces.go    # MessageWriter, MessageReader for tests
│   │
│   ├── db/                  # Rename from database; single place for GORM
│   │   ├── postgres.go      # Connect(cfg) (*gorm.DB, error)
│   │   └── models.go        # GORM model structs only (Tracker, Gateway, Zone, etc.)
│   │
│   ├── client/              # Rename from apiclient; external HTTP (auth, infer, etc.)
│   │   ├── auth.go
│   │   ├── data.go
│   │   └── updatedb.go
│   │
│   ├── api/                 # NEW: HTTP surface for server only
│   │   ├── handler/         # Handlers (move from controller); receive a Server or deps struct
│   │   │   ├── gateways.go
│   │   │   ├── zones.go
│   │   │   ├── trackers.go
│   │   │   ├── trackerzones.go
│   │   │   ├── parser.go
│   │   │   ├── settings.go
│   │   │   ├── tracks.go
│   │   │   └── health.go     # /health, /ready
│   │   ├── middleware/      # CORS, logging, recovery, request ID
│   │   └── response/        # JSON success/error helpers
│   │
│   ├── service/             # Keep; make depend on interfaces
│   │   ├── beacon.go        # LocationToBeaconService (depends on DB + Writer interfaces)
│   │   ├── parser.go
│   │   └── location.go      # NEW: Filter algorithm, AssignBeaconToList (from cmd/location)
│   │
│   ├── location/            # NEW: location service internals
│   │   ├── filter.go        # getLikelyLocations logic (score, confidence, write)
│   │   ├── assign.go        # assignBeaconToList
│   │   └── inference.go    # Interface for “get AI position”; adapter over client
│   │
│   ├── bridge/             # NEW: bridge-specific processing
│   │   ├── mqtt.go          # MQTT client options, connect, subscribe (thin wrapper)
│   │   └── handler.go       # MQTT message → Kafka (mqtthandler logic)
│   │
│   └── decoder/            # NEW: decoder-specific processing (or under service/)
│   │   ├── process.go       # ProcessIncoming, DecodeBeacon (from cmd/decoder)
│   │   └── registry.go      # Optional: wrap ParserRegistry with add/delete/update
│   │
├── app/                    # NEW (optional): per-service composition / “application” layer
│   ├── server/
│   │   ├── app.go           # ServerApp: config, db, kafka, router, event loop, shutdown
│   │   ├── routes.go        # Register all routes with deps
│   │   └── events.go        # RunEventLoop(ctx): location, alertbeacons, ticker
│   ├── location/
│   │   ├── app.go           # LocationApp: config, kafka, store, filter/inference, run loop
│   │   └── loop.go          # Run(ctx): ticker + channels
│   ├── bridge/
│   │   ├── app.go           # BridgeApp: config, kafka, mqtt, store, run loop
│   │   └── loop.go
│   └── decoder/
│       ├── app.go           # DecoderApp: config, kafka, registry, run loop
│       └── loop.go
```

You can adopt this incrementally: e.g. first add `internal/app/server` and move event loop + route registration there, then do the same for location/bridge/decoder.

### 3.2 What Each `cmd/<service>/main.go` Becomes

- **cmd/server/main.go:**  
  Load config (or exit on error), create logger, call `serverapp.New(cfg, logger)` (or bootstrap), then `app.Run(ctx)` and `app.Shutdown()`. No DB seed or parser sync in main—move those into `ServerApp` constructor or a `ServerApp.Init(ctx)`.

- **cmd/location/main.go:**  
  Load config, create logger, create AppState, call `locationapp.New(cfg, logger, appState)` (and optionally Kafka manager from bootstrap), then `app.Run(ctx)` and `app.Shutdown()`.

- **cmd/bridge/main.go:**  
  Same idea: bootstrap, then `bridgeapp.New(...)`, `Run(ctx)`, `Shutdown()`.

- **cmd/decoder/main.go:**  
  Bootstrap, then `decoderapp.New(...)` with parser registry, `Run(ctx)`, `Shutdown()`.

So each `main.go` is on the order of 20–40 lines: config + logger + optional bootstrap, build app, run, shutdown.

---

## 4. Refactoring Steps (Concrete)

### Phase 1: Extract bootstrap and shrink main (high impact, low risk)

1. **Add `internal/pkg/bootstrap` (or `runtime`):**
   - `Bootstrap(ctx) (cfg *config.Config, log *slog.Logger, kafka *kafkaclient.KafkaManager, cleanup func())` for a given service type (or one function per service that returns what that service needs).
   - Use it from all four `main.go` so that “create logger + kafka + context” is one place.
2. **Move shutdown sequence into a single place:** e.g. `Shutdown(ctx, kafkaManager, cleanup)` so each main just calls it after breaking the loop.

After this, each `main` is: load config → bootstrap → build Kafka/channels (or get from app) → create “App” (see Phase 2) → run loop → shutdown.

### Phase 2: Move event loops and “server wiring” into `internal/app`

3. **Server**
   - Add `internal/app/server`: `ServerApp` struct holding cfg, db, kafkaManager, channels, router, server, wg.
   - Move config load + DB connect + parser sync + Kafka reader setup into `NewServerApp(cfg, logger)` or `NewServerApp(...).Init(ctx)`.
   - Move route registration into `RegisterRoutes(app *ServerApp)` (or `app.Routes()` that returns `http.Handler`).
   - Move the event loop (select over chLoc, chEvents, ticker) into `ServerApp.RunEventLoop(ctx)`.
   - `main` becomes: config → bootstrap → NewServerApp → Init → go ListenAndServe → RunEventLoop(ctx) → Shutdown.
4. **Location**
   - Add `internal/app/location`: `LocationApp` with kafkaManager, appState, channels, filter algo, inference client.
   - Move `getLikelyLocations` and `assignBeaconToList` into `internal/pkg/service/location.go` or `internal/pkg/location/filter.go` and `assign.go`.
   - Move `getAI` behind an interface `LocationInferencer` in `internal/pkg/location`; implement with `client` (auth + infer).
   - Event loop in `LocationApp.Run(ctx)`.
5. **Bridge**
   - Add `internal/app/bridge`: `BridgeApp` with kafkaManager, mqtt client, appState, channels.
   - Move `mqtthandler` and MQTT subscribe into `internal/pkg/bridge/handler.go` and `mqtt.go`; call from app.
   - Event loop in `BridgeApp.Run(ctx)`.
6. **Decoder**
   - Add `internal/app/decoder`: `DecoderApp` with kafkaManager, parser registry, channels.
   - Move `processIncoming` and `decodeBeacon` into `internal/pkg/decoder/process.go`.
   - Event loop in `DecoderApp.Run(ctx)`.

This removes “too much inside main” and gives a single place per service to add features (the `app` and the packages it uses).

### Phase 3: Interfaces and dependency injection

7. **Messaging**
   - In `internal/pkg/messaging` (or keep `kafkaclient` and add interfaces there), define e.g. `MessageWriter` and `MessageReader` interfaces.
   - Have `KafkaManager` (or a thin wrapper) implement them so handlers and services accept interfaces; tests can inject fakes.
8. **Store**
   - Rename or keep `appcontext`; if you introduce `store`, have `AppState` implement e.g. `BeaconStore` / `SettingsStore` so location and bridge depend on interfaces.
9. **Server handlers**
   - Instead of passing `db`, `writer`, `ctx` to each handler, introduce a `Server` or `HandlerEnv` struct that holds DB, writers, and optionally logger; handlers become methods or receive this struct. Then you can add health checks and middleware in one place.

### Phase 4: Domain and API clarity

10. **Domain**
    - Create `internal/pkg/domain` and move shared types from `model` into domain subpackages (beacon, parser, location, trackers, etc.). Keep `model` as an alias or migrate imports gradually so that “core types” live under domain and “GORM models” stay under `db` if you split them.
11. **API**
    - Move HTTP handlers from `controller` to `internal/pkg/api/handler`; add `api/response` for JSON and errors; add `api/middleware` for CORS, logging, recovery. Register routes in `app/server` using these handlers.

---

## 5. Summary Table

| Service   | Current main responsibilities     | After refactor: main does              | New home for logic                          |
|----------|-----------------------------------|----------------------------------------|---------------------------------------------|
| server   | Bootstrap, DB seed, routes, loop, shutdown | Config, bootstrap, NewServerApp, Run, Shutdown | `app/server` (event loop, routes), `api/handler`, `service` |
| location | Bootstrap, Kafka, loop, shutdown  | Config, bootstrap, NewLocationApp, Run, Shutdown | `app/location`, `service/location` or `pkg/location` (filter, assign, inference) |
| bridge   | Bootstrap, Kafka, MQTT, loop, shutdown | Config, bootstrap, NewBridgeApp, Run, Shutdown | `app/bridge`, `pkg/bridge` (mqtt, handler)   |
| decoder  | Bootstrap, Kafka, loop, shutdown | Config, bootstrap, NewDecoderApp, Run, Shutdown | `app/decoder`, `pkg/decoder` (process, decode) |

---

## 6. Benefits After Refactoring

- **Reusability:** Location algorithm, bridge MQTT handling, and decoder logic live in `internal/pkg` and can be tested and reused without running a full `main`.
- **Separation:** `cmd` only composes and runs; `internal/app` owns per-service lifecycle and event loops; `internal/pkg` holds domain, store, messaging, API, and services.
- **Maintainability:** Adding a new route or a new Kafka consumer is “add to ServerApp and register”; adding a new algorithm is “add to location package and call from LocationApp.”
- **Testability:** Event loops and handlers can be unit-tested with fake writers/stores; integration tests can build `*ServerApp` or `*DecoderApp` with test doubles.
- **Consistency:** One bootstrap and one shutdown pattern across all four services; same style of “App” struct and `Run(ctx)`.

You can implement Phase 1 and Phase 2 first (bootstrap + app with event loops and moved logic), then Phase 3 (interfaces) and Phase 4 (domain + API) as follow-ups.
