Shishkin Pavel
Shishkin Pavel

Reputation: 351

request - response domain modeling in f#

I'm seeking for best practices in modeling request-response based system using functional approach in F#.

Generalized case requirements for example:

As for now, I end up with following design:

type CommonData = {...}

type RequestMessage =
| ARequestMessage of CommonData * ARequestData
| BRequestMessage of CommonData * BRequestData
...

type Response<'S, 'F> =
| Success of 'S
| Failure of 'F

type ResponseMessage =
| AResponseMessage of Response<AResponseSuccess, AResponseFailure>
| BResponseMessage of Response<BResponseSuccess, BResponseFailure>
...

where AResponseSuccess and BResponseSuccess contains same discriminated union cases or ResponseMessage type is covered by GeneralResponseMessage type like

type GeneralResponseMessage =
| CommonFailure of CommonResponseFailure
| SpecificResponse of ResponseMessage

both cases look terrible and I can't figure out more clear and elegant solution.

Edit

System processes requests in statemachine-manner over single evolving state, so it isn't possible to separate different types of request processing

Upvotes: 3

Views: 156

Answers (1)

Honza Brestan
Honza Brestan

Reputation: 10947

This may be better suited for https://codereview.stackexchange.com/, but let me try to answer this here.

My take on this would be to group the operations vertically, not horizontally. What I mean by that is that instead of grouping all requests in one type and all responses in another, I would pair up the corresponding request and response types, and build a generic wrapper for the processing itself.

In your case, the processing fundamentally is a function of type RequestMessage -> GeneralResponseMessage. I propose generalizing this into the following:

type ProcessRequest<'requestData, 'responseMessage, 'responseFailure> =
    CommonData -> 'requestData ->
        Result<'responseMessage, ErrorResponse<'responseFailure>>

and ErrorResponse<'responseFailure> = 
    | CommonFailure of CommonResponseFailure
    | SpecificResponse of 'responseFailure

Just a note here, I use Result from FSharp.Core instead of your Response type, as they model the exact same thing.

Then you can create a specific instances, for example

type AProcessRequest =
    ProcessRequest<ARequestMessage, AResponseMessage, AResponseFailure>

And this can then be used to process specific messages. In your case, the processing logic would have to match on RequestMessage case and proceed based on that, in my case you have to this match before calling the specific processor, but if you need it grouped in one type, you can always do something this to supply all the implementations as a single piece of data

type ProcessMessage =
    { A : AProcessRequest
      B : BProcessRequest
      C : ... }

and you can supply the implementations in composition root.

If you don't like so many specific types, you don't have to give names to the concrete instances of the generic processor, C : ProcessMessage<CRequestMessage, CResponseMessage, CResponseFailure> will do just fine, but may be less readable if there are many instances of similar types.

Upvotes: 2

Related Questions