@@ -169,11 +169,175 @@ class MyPluginListener {
169169}
170170```
171171
172+ ## Request-Response Pattern
173+
174+ In addition to the event bus, surf-redis supports request-response patterns where a server can send a request and wait for a response with timeout support.
175+
176+ ### Quick Start
177+
178+ ``` kotlin
179+ import kotlinx.coroutines.runBlocking
180+ import kotlinx.coroutines.launch
181+ import de.slne.redis.request.*
182+
183+ // 1. Create your request and response (must be @Serializable)
184+ @Serializable
185+ data class GetPlayerRequest (val minLevel : Int ) : RedisRequest()
186+
187+ @Serializable
188+ data class PlayerListResponse (val players : List <String >) : RedisResponse()
189+
190+ // 2. Create a request handler
191+ class PlayerRequestHandler {
192+ @RequestHandler
193+ fun handlePlayerRequest (context : RequestContext <GetPlayerRequest >) {
194+ // Launch coroutine to respond asynchronously
195+ context.coroutineScope.launch {
196+ val players = fetchPlayersAsync(context.request.minLevel)
197+ context.respond(PlayerListResponse (players))
198+ }
199+ }
200+ }
201+
202+ // 3. Set up and use
203+ runBlocking {
204+ val bus = RequestResponseBus (" redis://localhost:6379" )
205+
206+ // Register handler (ServerA)
207+ bus.registerRequestHandler(PlayerRequestHandler ())
208+
209+ // Send request and wait for response (ServerB or same server)
210+ val response = bus.sendRequest<PlayerListResponse >(
211+ GetPlayerRequest (minLevel = 5 ),
212+ timeoutMs = 3000 // Default timeout is 3 seconds
213+ )
214+ println (" Players: ${response.players} " )
215+ }
216+ ```
217+
218+ ### Features
219+
220+ - 🔄 ** Request-Response Pattern** : Send requests and receive typed responses
221+ - ⏱️ ** Timeout Support** : Configurable timeout (default 3 seconds)
222+ - 🔀 ** Bidirectional** : Any server can both send requests and respond to requests
223+ - 🚀 ** Flexible Async** : Handlers control when to launch coroutines (not forced to be suspend)
224+ - 🎯 ** Type-safe** : Request and response types are validated at compile time
225+
226+ ### Usage
227+
228+ #### 1. Create Request and Response Classes
229+
230+ ``` kotlin
231+ import de.slne.redis.request.RedisRequest
232+ import de.slne.redis.request.RedisResponse
233+ import kotlinx.serialization.Serializable
234+
235+ @Serializable
236+ data class GetPlayerRequest (val minLevel : Int ) : RedisRequest()
237+
238+ @Serializable
239+ data class PlayerListResponse (val players : List <String >) : RedisResponse()
240+ ```
241+
242+ #### 2. Create Request Handlers
243+
244+ Handlers receive a ` RequestContext ` that provides:
245+ - ` request ` : The incoming request
246+ - ` coroutineScope ` : Scope for launching coroutines if needed
247+ - ` respond(response) ` : Method to send the response
248+
249+ ``` kotlin
250+ import de.slne.redis.request.RequestHandler
251+ import de.slne.redis.request.RequestContext
252+ import kotlinx.coroutines.launch
253+
254+ class MyRequestHandler {
255+ @RequestHandler
256+ fun handlePlayerRequest (context : RequestContext <GetPlayerRequest >) {
257+ // Launch coroutine for async operations
258+ context.coroutineScope.launch {
259+ val players = fetchPlayersFromDatabaseAsync(context.request.minLevel)
260+ context.respond(PlayerListResponse (players))
261+ }
262+ }
263+ }
264+ ```
265+
266+ #### 3. Register Handlers and Send Requests
267+
268+ ``` kotlin
269+ import de.slne.redis.request.RequestResponseBus
270+ import kotlinx.coroutines.runBlocking
271+
272+ fun main () = runBlocking {
273+ val bus = RequestResponseBus (" redis://localhost:6379" )
274+
275+ // Register handler (this server will respond to requests)
276+ bus.registerRequestHandler(MyRequestHandler ())
277+
278+ // Send request and wait for response (with timeout)
279+ try {
280+ val response = bus.sendRequest<PlayerListResponse >(
281+ GetPlayerRequest (minLevel = 10 ),
282+ timeoutMs = 3000
283+ )
284+ println (" Received ${response.players.size} players" )
285+ } catch (e: RequestTimeoutException ) {
286+ println (" Request timed out: ${e.message} " )
287+ }
288+
289+ bus.close()
290+ }
291+ ```
292+
293+ ### Request-Response vs Events
294+
295+ ** Use Request-Response when:**
296+ - You need a reply/acknowledgment
297+ - You need data back from another server
298+ - You need to know if the operation succeeded
299+ - You want timeout handling
300+
301+ ** Use Events when:**
302+ - You want to broadcast information
303+ - You don't need a reply
304+ - Multiple servers should react to the same event
305+ - Fire-and-forget pattern is acceptable
306+
307+ ### Example Scenario
308+
309+ ** ServerA** (Lobby Server) and ** ServerB** (Game Server):
310+
311+ ``` kotlin
312+ // ServerA can handle requests
313+ class LobbyHandler {
314+ @RequestHandler
315+ fun getServerStatus (context : RequestContext <ServerStatusRequest >) {
316+ // Respond using coroutine scope
317+ context.coroutineScope.launch {
318+ context.respond(ServerStatusResponse (" Lobby-1" , online = true , playerCount = 42 ))
319+ }
320+ }
321+ }
322+
323+ // ServerB can also send requests to ServerA
324+ val response = bus.sendRequest<ServerStatusResponse >(
325+ ServerStatusRequest (" Lobby-1" ),
326+ timeoutMs = 3000
327+ )
328+ ```
329+
330+ Both servers can simultaneously:
331+ - Send requests to other servers
332+ - Respond to requests from other servers
333+
172334## Example
173335
174336See the ` de.slne.redis.example ` package for complete examples:
175337- ` ExampleEvents.kt ` - Example event definitions
176338- ` ExampleUsage.kt ` - Example usage demonstrating async publishing and subscribing
339+ - ` ExampleRequests.kt ` - Example request/response definitions
340+ - ` ExampleRequestResponseUsage.kt ` - Example usage demonstrating request-response pattern
177341
178342## API Reference
179343
@@ -202,6 +366,46 @@ Main class for managing events with async support.
202366- ` fun unregisterListener(listener: Any) ` - Unregister a listener and all its handlers
203367- ` fun close() ` - Close connections and clean up resources
204368
369+ ### RedisRequest
370+
371+ Base class for all requests. Extend this to create custom requests. Must be annotated with ` @Serializable ` .
372+
373+ ### RedisResponse
374+
375+ Base class for all responses. Extend this to create custom responses. Must be annotated with ` @Serializable ` .
376+
377+ ### @RequestHandler
378+
379+ Annotation for marking request handler methods. Methods must:
380+ - Have exactly one parameter of type ` RequestContext<TRequest> `
381+ - Return void (Unit)
382+ - Handler controls when/how to respond using ` context.respond() `
383+
384+ ### RequestContext<TRequest >
385+
386+ Context object provided to request handlers containing:
387+ - ` request: TRequest ` - The incoming request
388+ - ` coroutineScope: CoroutineScope ` - Scope for launching coroutines if needed
389+ - ` suspend fun respond(response: RedisResponse) ` - Send the response
390+
391+ ### RequestResponseBus
392+
393+ Main class for managing request-response patterns with async support and timeout handling.
394+
395+ ** Constructor:**
396+ - ` RequestResponseBus(redisUri: String, coroutineScope: CoroutineScope = ...) ` - Create a new request-response bus connected to Redis
397+
398+ ** Methods:**
399+ - ` suspend fun <T : RedisResponse> sendRequest(request: RedisRequest, timeoutMs: Long = 3000): T ` - Send a request and wait for response asynchronously
400+ - ` fun <T : RedisResponse> sendRequestBlocking(request: RedisRequest, timeoutMs: Long = 3000): T ` - Send a request and wait for response synchronously (blocking)
401+ - ` fun registerRequestHandler(handler: Any) ` - Register an object with @RequestHandler methods
402+ - ` fun unregisterRequestHandler(handler: Any) ` - Unregister a handler and all its request handlers
403+ - ` fun close() ` - Close connections and clean up resources
404+
405+ ### RequestTimeoutException
406+
407+ Exception thrown when a request times out without receiving a response.
408+
205409## License
206410
207411This project is open source and available under the MIT License.
0 commit comments