68 lines
2.1 KiB
Python
68 lines
2.1 KiB
Python
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
from typing import Any, Protocol
|
||
|
|
|
||
|
|
from .contracts import KernelTransition, TradeSlot
|
||
|
|
from .control import KernelControlSnapshot
|
||
|
|
from .journal import _transition_row
|
||
|
|
from .projection import build_position_state_row
|
||
|
|
from .utils import json_safe
|
||
|
|
|
||
|
|
|
||
|
|
class HazelcastClientLike(Protocol):
|
||
|
|
def get_map(self, name: str): ...
|
||
|
|
def get_topic(self, name: str): ...
|
||
|
|
|
||
|
|
|
||
|
|
class HazelcastProjector:
|
||
|
|
"""Durable BLUE/PINK-compatible projection mirror."""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
client: HazelcastClientLike | None = None,
|
||
|
|
*,
|
||
|
|
active_slots_map: str = "dita_active_slots",
|
||
|
|
events_topic: str = "dita_trade_events",
|
||
|
|
) -> None:
|
||
|
|
self.client = client
|
||
|
|
self.active_slots_map = active_slots_map
|
||
|
|
self.events_topic = events_topic
|
||
|
|
|
||
|
|
def publish_slot(self, slot: TradeSlot) -> None:
|
||
|
|
if self.client is None:
|
||
|
|
return
|
||
|
|
self.client.get_map(self.active_slots_map).put(slot.trade_id, build_position_state_row(slot))
|
||
|
|
|
||
|
|
def publish_event(self, event_type: str, payload: dict[str, Any]) -> None:
|
||
|
|
if self.client is None:
|
||
|
|
return
|
||
|
|
topic = self.client.get_topic(self.events_topic)
|
||
|
|
topic.publish(
|
||
|
|
json.dumps(
|
||
|
|
{"event_type": event_type, "payload": json_safe(payload)},
|
||
|
|
ensure_ascii=False,
|
||
|
|
sort_keys=True,
|
||
|
|
default=str,
|
||
|
|
)
|
||
|
|
)
|
||
|
|
|
||
|
|
|
||
|
|
class HazelcastRowWriter:
|
||
|
|
"""Callback bridge for ``HazelcastProjection`` writer hooks."""
|
||
|
|
|
||
|
|
def __init__(self, client: HazelcastClientLike) -> None:
|
||
|
|
self.client = client
|
||
|
|
|
||
|
|
def __call__(self, name: str, row: dict[str, Any]) -> None:
|
||
|
|
if name.endswith("trade_events"):
|
||
|
|
self.client.get_topic(name).publish(
|
||
|
|
json.dumps(row, ensure_ascii=False, sort_keys=True, default=str)
|
||
|
|
)
|
||
|
|
return
|
||
|
|
if name.endswith("control"):
|
||
|
|
key = "control"
|
||
|
|
else:
|
||
|
|
key = str(row.get("trade_id", row.get("slot_id", row.get("event_id", ""))))
|
||
|
|
self.client.get_map(name).put(key, json_safe(row))
|