Request -> Handler -> SubPub Pattern with MediatR or Raw F# code
Tags: dotnet, designpatterns, fsharp
Published: 2024-05-10 05:51:54

We will try:
- MediatR 
- Mailbox 
- Event
-  Rx
-  Agent


## MediatR 

Example:
```fsharp
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`
```
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:**
```fsharp
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())

```

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:

```fsharp
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"
```

Running result:
```bash
Hello John  
User John was greeted
```

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:

```fsharp

// 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" 
```

## Listing other methods:

For example, you can use:

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

```

Then define the event handling function as a delegate of this type:
 ```fsharp
let handler (s,e) = 
  printfn "Handling event: %A" e
```
Subscribe to the event:
```fsharp
let event = Event<UserGreetedEvent>()
event.Publish(handler)

```
Publish the event:
```fsharp
event.Trigger(this, {Name = "John"})
```
2. Rx (Reactive Extensions) can use Rx's Subject to replace events:
```fsharp
let subject = new Subject<'T>()

subject.OnNext(event) |> ignore

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

agent.Post(event)
```
End of the article, discussion is welcome.

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