DEV Community

Rocky LIU Yan
Rocky LIU Yan

Posted on

Request -> Handler -> SubPub Pattern with MediatR or Raw F# code

We will try:

  • MediatR
  • Mailbox
  • Event
  • Rx
  • Agent

MediatR

Example:

open MediatR

// Define the request type inheriting from `IRequest`. Here it's `MyRequest`.
type MyRequest() = 
  inherit IRequest

// Define the handler that takes the request as a parameter.
let handler (req: MyRequest) = 
  printfn "Handling request"

// Get the singleton instance of `Mediator`.
let mediator = Mediator.Instance

// Create the request.
let req = MyRequest()

// Use `mediator.Send` to send the request and pipe the return value to the `handler`.
mediator.Send(req) |> handler`
Enter fullscreen mode Exit fullscreen mode

Main steps:

  • Define a request type inheriting from IRequest. Here it's MyRequest.
  • Define a handler function handler that takes a request as a parameter.
  • Obtain a singleton instance of Mediator.
  • Create a request req.
  • Use mediator.Send to send the request and pass the return value through the handler.
  • This allows you to use MediatR's pipeline to send commands or requests and handle them. You can continue to expand by adding more request types, handlers, etc.

Next, design a subscription:

open MediatR

// Define the event type inheriting from `INotification`. Here it's `MyEvent`.
type MyEvent() = 
  inherit INotification

// Define the event handler function.
let handler (evt: MyEvent) =
  printfn "Handling event"

let mediator = Mediator.Instance  

// Subscribe to the event.
mediator.Subscribe(typeof<MyEvent>, handler)

// Publish the event.
mediator.Publish(MyEvent())

Enter fullscreen mode Exit fullscreen mode

Main steps:

  • Define an event type inheriting from INotification. Here it's MyEvent.
  • Define an event handler function handler that takes an event as a parameter.
  • Get the singleton instance of Mediator.
  • Use mediator.Subscribe to subscribe to the event, specifying the event type and handler.
  • Use mediator.Publish to publish the event.

This allows you to implement a publish-subscribe pattern. You can continue to expand by adding more event types, handlers, etc.

Combining them: Using F# functional programming style, combine the request handler and event subscription into a MediatR example:

open MediatR

// Define the request.
type SayHelloRequest(name: string) =
  inherit Request
  member this.Name = name

// Define the handler.
let sayHelloHandler (request: SayHelloRequest) =
  printfn "Hello %s" request.Name

// Define the event.
type UserGreetedEvent(name: string) =
  inherit Notification
  member this.Name = name 

// Define the event handler.
let userGreetedHandler (evt: UserGreetedEvent) =
  printfn "User %s was greeted" evt.Name

// Request handling process.
let sendHello name mediator =
  let request = SayHelloRequest(name)  
  mediator.Send(request) |> sayHelloHandler

// Event subscription.
let subscribe mediator =
  mediator.Subscribe(typeof<UserGreetedEvent>, userGreetedHandler)

// Orchestrator function combining calls.
let orchestrator mediator name =
  subscribe mediator
  sendHello name mediator
  mediator.Publish(UserGreetedEvent(name))

// Get the Mediator and run.
let mediator = Mediator.Instance
orchestrator mediator "John"
Enter fullscreen mode Exit fullscreen mode

Running result:

Hello John  
User John was greeted
Enter fullscreen mode Exit fullscreen mode

By calling MediatR's Send method to send a request, Subscribe method to subscribe to an event, and Publish method to publish an event, you can implement both request-response and publish-subscribe patterns.

F# raw code

Can F# bare code implement the request and subscription pattern?

Certainly. You can use MailboxProcessor, but the encapsulation and ease of use are not as good as MediatR.

Here's an example of implementing subscriptions with MailboxProcessor without using MediatR, just using F# language features:


// Define the request type.
type SayHelloRequest = {
  Name: string
}

// Define the event type.
type UserGreetedEvent = {
  Name: string
}

// Request handling function.
let sayHelloHandler (request: SayHelloRequest) =
  printfn "Hello %s" request.Name

// Event handling function.
let userGreetedHandler (event: UserGreetedEvent) =
  printfn "User %s was greeted" event.Name 

// Use a pipeline to send a request.
let sendRequest name =
  { Name = name } |> sayHelloHandler

// Use event subscription with `MailboxProcessor`.
let publisher = MailboxProcessor.Start(fun inbox ->
  let rec loop() = async {
    let! event = inbox.Receive() 
    userGreetedHandler event
    return! loop()  
  }
  loop()
)

// Publish an event.
let publish name =
  publisher.Post({Name = name})

// Orchestrator.
let orchestrator name =
  sendRequest name
  publish name

orchestrator "John" 
Enter fullscreen mode Exit fullscreen mode

Listing other methods:

For example, you can use:

  1. The Event type:
type Event<'T> = delegate of sender: obj * evt: 'T -> unit

Enter fullscreen mode Exit fullscreen mode

Then define the event handling function as a delegate of this type:

let handler (s,e) = 
  printfn "Handling event: %A" e
Enter fullscreen mode Exit fullscreen mode

Subscribe to the event:

let event = Event<UserGreetedEvent>()
event.Publish(handler)

Enter fullscreen mode Exit fullscreen mode

Publish the event:

event.Trigger(this, {Name = "John"})
Enter fullscreen mode Exit fullscreen mode
  1. Rx (Reactive Extensions) can use Rx's Subject to replace events:
let subject = new Subject<'T>()

subject.OnNext(event) |> ignore

subject.Subscribe(handler) 
Enter fullscreen mode Exit fullscreen mode
  1. Agent can use Agent's Post and Receive for message passing:
let agent = Agent.Start(fun inbox -> 
  let rec loop() = 
    async {
      let! msg = inbox.Receive()
      handler msg
      return! loop()
    }
  loop()  
)

agent.Post(event)
Enter fullscreen mode Exit fullscreen mode

End of the article, discussion is welcome.

The code is for reference only. Please modify it yourself if you want to run it.

Top comments (0)