F# SAFE how to deal with growth of message DU variants?

109 Views Asked by At

In F# SAFE stack there is a DU type, which defines all types of messages flowing between server and client. In sample application there are <10 of them, I looked on provided examples - all of them have not many possible types. But what if the application is not small - there will be hundreds of types in DU; which will become hard to maintain. So I decided to divide this type in sub-types and put logic in corresponding files.

Here is my simplified type:

 type Msg2 =
    | LoginMsg of LoginState
    | RegisterMsg of RegisterState
    | ViewUpdateMsg of ViewUpdateState

Login state is defined in another file:

 type LoginState =
        | Login
        | LogInResult of LoginResult

Login module works with logins:

let workWithLogin (model: Model) (msg: LoginState) (todosApi: ITodosApi) : Model * Cmd<LoginState> =
    match msg with
    | LoginState.Login ->
        let result =
            Cmd.OfAsync.perform todosApi.login model.InputData LoginState.LogInResult

        model, result
    | LoginState.LogInResult data ->
        { model with
              LoginState = getVal data.Message
              Token = data.Token },
        Cmd.none

Note that it returns Model * Cmd<LoginState>.

Now I need to fix my update function:

let update2 (msg: Msg2) (model: Model) : Model * Cmd<Msg2> =
     match msg with
     | LoginMsg n ->
         let ret = workWithLogin model n todosApi
         model, snd ret
     | RegisterMsg n -> ...
     | ViewUpdateMsg n ->  ...

The problem here is that I get Cmd<LoginState> from login module and need to convert it to Cmd<Msg2> somehow. enter image description here So I either need to create new Msg2 type or convert DU variant LoginMsg of LoginState to Msg2. I dont understand how to get LogInResult data from Cmd<LoginState>.

How can I fix this? How the problem with many message types is solved in big projects?

1

There are 1 best solutions below

0
On BEST ANSWER

What you've done with wrapping in "sub"-messages is the right way to go, and you should probably do the same thing with the model - i.e. let Login have it's own model. To convert between different Cmd<'msg> there is a Cmd.map. In your case you can:

let update2 (msg: Msg2) (model: Model) : Model * Cmd<Msg2> =
  match msg with
  | LoginMsg n ->
      let (loginModel, loginCmd) = workWithLogin model n todosApi
      { model with Login = loginModel }, Cmd.map LoginMsg loginCmd

You can see some API description of Cmd here: https://elmish.github.io/elmish/cmd.html