|
1 | 1 | package zio.kvstore |
2 | 2 |
|
3 | | -import zio.raft.{Command, Index, HMap} |
4 | | -import zio.raft.sessionstatemachine.{SessionStateMachine, ScodecSerialization, ServerRequestForSession, StateWriter} |
5 | | -import zio.raft.sessionstatemachine.asResponseType // Extension method |
6 | | -import zio.raft.protocol.SessionId |
7 | | -import zio.{ZIO} |
8 | | -import zio.prelude.Newtype |
9 | | - |
10 | | -import scodec.Codec |
11 | | -import scodec.codecs.{ascii, discriminated, fixedSizeBytes, utf8_32} |
12 | | -import java.time.Instant |
13 | | - |
14 | | -// Simple KV store - no sessions, no server requests |
15 | | -// We just use the session framework for the template pattern and HMap |
16 | | - |
17 | | -sealed trait KVCommand extends Command |
18 | | - |
19 | | -object KVCommand: |
20 | | - case class Set(key: String, value: String) extends KVCommand: |
21 | | - type Response = SetDone |
22 | | - |
23 | | - case class Get(key: String) extends KVCommand: |
24 | | - type Response = GetResult |
25 | | - |
26 | | - // Codec for KVCommand (needed by ZmqRpc) |
27 | | - val getCodec = utf8_32.as[Get] |
28 | | - val setCodec = (utf8_32 :: utf8_32).as[Set] |
29 | | - given commandCodec: Codec[KVCommand] = discriminated[KVCommand] |
30 | | - .by(fixedSizeBytes(1, ascii)) |
31 | | - .typecase("S", setCodec) |
32 | | - .typecase("G", getCodec) |
33 | | - |
34 | | -// Response marker type - encompasses all KV command responses |
35 | | -sealed trait KVResponse |
36 | | -case class SetDone() extends KVResponse |
37 | | -case class GetResult(value: Option[String]) extends KVResponse |
38 | | - |
39 | | -// KV Schema - single prefix for key-value data |
40 | | -object KVKey extends Newtype[String] |
41 | | -type KVKey = KVKey.Type |
42 | | -given HMap.KeyLike[KVKey] = HMap.KeyLike.forNewtype(KVKey) |
43 | | - |
44 | | -type KVSchema = ("kv", KVKey, String) *: EmptyTuple |
45 | | -type KVCompleteSchema = Tuple.Concat[zio.raft.sessionstatemachine.SessionSchema[KVResponse, KVServerRequest], KVSchema] |
46 | | - |
47 | | -// Dummy server request type (KV store doesn't use server requests) |
48 | | -case class NoServerRequest() |
49 | | - |
50 | | -type KVServerRequest = NoServerRequest |
51 | | - |
52 | | -// KV store with scodec serialization mixin |
53 | | -class KVStateMachine extends SessionStateMachine[KVCommand, KVResponse, KVServerRequest, KVSchema] |
54 | | - with ScodecSerialization[KVResponse, KVServerRequest, KVSchema]: |
55 | | - |
56 | | - // Import provided codecs from Codecs object |
57 | | - import zio.raft.sessionstatemachine.Codecs.{sessionMetadataCodec, requestIdCodec, pendingServerRequestCodec} |
58 | | - |
59 | | - // Provide codecs for response types |
60 | | - given Codec[SetDone] = scodec.codecs.provide(SetDone()) |
61 | | - given Codec[GetResult] = scodec.codecs.optional(scodec.codecs.bool, utf8_32).xmap( |
62 | | - opt => GetResult(opt), |
63 | | - gr => gr.value |
64 | | - ) |
65 | | - |
66 | | - // Provide codec for response marker type |
67 | | - given Codec[KVResponse] = discriminated[KVResponse] |
68 | | - .by(fixedSizeBytes(1, ascii)) |
69 | | - .typecase("S", summon[Codec[SetDone]]) |
70 | | - .typecase("G", summon[Codec[GetResult]]) |
71 | | - |
72 | | - // Provide codec for our value types |
73 | | - given Codec[String] = utf8_32 |
74 | | - |
75 | | - // Dummy codec for NoServerRequest |
76 | | - given Codec[NoServerRequest] = scodec.codecs.provide(NoServerRequest()) |
77 | | - |
78 | | - val codecs = summon[HMap.TypeclassMap[Schema, Codec]] |
79 | | - |
80 | | - protected def applyCommand( |
81 | | - createdAt: Instant, |
82 | | - sessionId: SessionId, |
83 | | - command: KVCommand |
84 | | - ): StateWriter[HMap[KVCompleteSchema], ServerRequestForSession[KVServerRequest], command.Response & KVResponse] = |
85 | | - command match |
86 | | - case set @ KVCommand.Set(key, value) => |
87 | | - for |
88 | | - state <- StateWriter.get[HMap[KVCompleteSchema]] |
89 | | - newState = state.updated["kv"](KVKey(key), value) |
90 | | - _ <- StateWriter.set(newState) |
91 | | - yield SetDone().asResponseType(command, set) |
92 | | - |
93 | | - case get @ KVCommand.Get(key) => |
94 | | - for |
95 | | - state <- StateWriter.get[HMap[KVCompleteSchema]] |
96 | | - result = state.get["kv"](KVKey(key)) |
97 | | - yield GetResult(result).asResponseType(command, get) |
98 | | - |
99 | | - protected def handleSessionCreated( |
100 | | - createdAt: Instant, |
101 | | - sessionId: SessionId, |
102 | | - capabilities: Map[String, String] |
103 | | - ): StateWriter[HMap[KVCompleteSchema], ServerRequestForSession[KVServerRequest], Unit] = |
104 | | - StateWriter.succeed(()) |
105 | | - |
106 | | - protected def handleSessionExpired( |
107 | | - createdAt: Instant, |
108 | | - sessionId: SessionId, |
109 | | - capabilities: Map[String, String] |
110 | | - ): StateWriter[HMap[KVCompleteSchema], ServerRequestForSession[KVServerRequest], Unit] = |
111 | | - StateWriter.succeed(()) |
112 | | - |
113 | | - // takeSnapshot and restoreFromSnapshot are now provided by SessionStateMachine base class! |
114 | | - // They use the TypeclassMap[Schema, Codec] passed in constructor |
115 | | - |
116 | | - override def shouldTakeSnapshot(lastSnapshotIndex: Index, lastSnapshotSize: Long, commitIndex: Index): Boolean = |
117 | | - false // Disable snapshots for now |
118 | | -end KVStateMachine |
119 | | - |
120 | | -// HttpServer commented out for now - SessionCommand type system complexity |
121 | | -// The KVStateMachine implementation demonstrates: |
122 | | -// - SessionStateMachine extension |
123 | | -// - HMap with typed schema |
124 | | -// - Scodec-based serialization |
125 | | -// - TypeclassMap usage |
126 | | - |
127 | | -// TODO: Implement proper HTTP server with session management |
| 3 | +import zio.ZIO |
128 | 4 |
|
129 | 5 | object KVStoreApp extends zio.ZIOAppDefault: |
130 | 6 | override def run = |
|
0 commit comments