Merge branch 'refactor' of https://github.com/noordstar/elm-matrix-sdk into refactor
commit
da0fe70def
|
@ -1,31 +1,105 @@
|
||||||
# Elm architecture
|
# Elm architecture
|
||||||
|
|
||||||
To support the complex ways that the Matrix API runs, standard Elm tasks have gained an increased amount of complexity.
|
Given that this document is rewritten during the refactor, this document is
|
||||||
This effectively:
|
intended as a comprehensive description of the HTTP Task build architecture of
|
||||||
|
the Elm SDK.
|
||||||
|
|
||||||
1. Helps the Elm compiler recognize mistakes.
|
## How it used to work
|
||||||
2. Helps the SDK developer chain multiple tasks together efficiently.
|
|
||||||
|
|
||||||
## How the Matrix tasks work
|
This section describes the old architecture. Writing a summary helps point out the missteps and the potential for optimization.
|
||||||
|
|
||||||
Whenever the user attempts to run a Matrix task, it has two types of information:
|
|
||||||
|
|
||||||
### Task input
|
|
||||||
|
|
||||||
The task input is input that the function uses to access information. It has the following properties:
|
|
||||||
|
|
||||||
- If the task is attempted at a later time, these values will remain unchanged.
|
|
||||||
- If these values do not exist, the task cannot be executed.
|
|
||||||
|
|
||||||
### Context
|
### Context
|
||||||
|
|
||||||
The context is the bundle of tokens, values and information that the Vault has at the moment. It has the following properties:
|
The `Context` type was defined as follows:
|
||||||
|
|
||||||
- If the task is attempted at a later time, these values will change according to the Vault's latest token collection.
|
```elm
|
||||||
- If these values do not exist, the task can get them as a sub-task before getting the actual data.
|
type Context a
|
||||||
|
= Context
|
||||||
|
{ accessToken : String
|
||||||
|
, baseUrl : String
|
||||||
|
, loginParts : Maybe LoginParts
|
||||||
|
, sentEvent : String
|
||||||
|
, timestamp : Timestamp
|
||||||
|
, transactionId : String
|
||||||
|
, userId : String
|
||||||
|
, versions : List String
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Task chains
|
Notice how `a` is a phantom type. The phantom type allows us to force the user to gather the correct data. For example, if our function requires the `accessToken` to be specified, we exclusively allow a `Context { a | accessToken : () }` type. This way, the compiler will ensure that the context always contains an access token before the function is run.
|
||||||
|
|
||||||
A task chain is a chain of tasks that are run in sequential order. Traditionally, in a chain of length `n`, the first `n-1` tasks add information to the context, and the last chain actually runs the task.
|
The `Context` type plays a central role in this architecture. It has a few upsides and downsides:
|
||||||
|
|
||||||
|
- ✅ The `Context` serves as a reliable representation of the vault's values. Even though the vault might not always have all information, the `Context`'s phantom types forces the developer to get all values that may not exist.
|
||||||
|
|
||||||
|
- ⛔ The`Context` is only a representation of the _current_ state. It fails to deliver prior information, like transaction ids that need to be remembered for failed executions.
|
||||||
|
|
||||||
|
### Task chains
|
||||||
|
|
||||||
|
Currently, every Matrix task is defined in a `TaskChain` alias:
|
||||||
|
|
||||||
|
```elm
|
||||||
|
type alias TaskChain err u a b =
|
||||||
|
Context a -> Task (FailedChainPiece err u) (TaskChainPiece u a b)
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, values `a` and `b` are phantom types. Value `err` represents an error type, and `u` represents a data type that updates our model.
|
||||||
|
|
||||||
|
Given a context (with a phantom type to ensure the presence of relevant info),
|
||||||
|
the function returns a `Task` that either breaks the chain or allows the
|
||||||
|
execution of another `TaskChain`, which is then combined using the
|
||||||
|
`Task.andThen` function.
|
||||||
|
|
||||||
|
To be exact, the two pieces of the `Task` were defined as follows:
|
||||||
|
|
||||||
|
#### Failed chain piece
|
||||||
|
|
||||||
|
The `FailedChainPiece` looks as follows:
|
||||||
|
|
||||||
|
```elm
|
||||||
|
type alias FailedChainPiece err u =
|
||||||
|
{ error : err, messages : List u }
|
||||||
|
```
|
||||||
|
|
||||||
|
There is no opportunity to change the progression of the chain, and everything stops here.
|
||||||
|
|
||||||
|
The library did offer functions to catch broken chains, and to fix them with a function that took the `err` value as an input and returned another task chain. However, generally speaking, a failed chain piece announces the end of a chain.
|
||||||
|
|
||||||
|
#### Task chain piece
|
||||||
|
|
||||||
|
The `TaskChainPiece` is a piece of the chain that has executed successfully. It looks as follows:
|
||||||
|
|
||||||
|
```elm
|
||||||
|
type alias TaskChainPiece u a b =
|
||||||
|
{ contextChange : Context a -> Context b
|
||||||
|
, messages : List u
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The piece offers both a function to change the existing context, if necessary, and provides a list of messages that can be returned.
|
||||||
|
|
||||||
|
Once the chain has finished, all the messages of all the chain pieces are collected, put together in one large list and returned to the user. Ideally, the user would then feed all of them back into the Matrix Vault.
|
||||||
|
|
||||||
|
|
||||||
|
## What are some of the main issues
|
||||||
|
|
||||||
|
- The `Context` fails to remember values like transaction ids in a proper way.
|
||||||
|
- There needs to be an easily accessible way to determine what went wrong in task chains.
|
||||||
|
- The vocabulary is difficult to understand and some of the data types are similar enough to suggest that bloatware might come in.
|
||||||
|
|
||||||
|
## How to refactor
|
||||||
|
|
||||||
|
First off, the `Context` type needs to be removed.
|
||||||
|
|
||||||
|
### Vault is the (new) context
|
||||||
|
|
||||||
|
While building the `Context`, its values are derived from two sources:
|
||||||
|
|
||||||
|
1. The `Vault` type stores the information.
|
||||||
|
2. The information is gathered from the Matrix API using available information from the `Vault`.
|
||||||
|
|
||||||
|
When building a task chain, we start with an empty context of type `Context {}` and values are slowly added to that. Instead, we can add a phantom type to the `Vault` and then use the `Vault {}` to build the context.
|
||||||
|
|
||||||
|
Effectively, this means we no longer need to specify a separate data type that stores any relevant information that is already available in the `Vault`. Instead, the `Vault`'s phantom type specifies that functions can be used if and only if certain values are available.
|
||||||
|
|
||||||
|
The phantom type is exclusively an internal type and will therefore never be communicated to the end user of the library.
|
||||||
|
|
|
@ -29,6 +29,7 @@ getEventInputV1 data context =
|
||||||
]
|
]
|
||||||
|> R.toTask SO1.clientEventDecoder
|
|> R.toTask SO1.clientEventDecoder
|
||||||
|
|
||||||
|
|
||||||
getEventInputV2 : GetEventInputV1 -> Context { a | accessToken : (), baseUrl : (), sentEvent : () } -> Task X.Error GetEventOutputV1
|
getEventInputV2 : GetEventInputV1 -> Context { a | accessToken : (), baseUrl : (), sentEvent : () } -> Task X.Error GetEventOutputV1
|
||||||
getEventInputV2 data context =
|
getEventInputV2 data context =
|
||||||
context
|
context
|
||||||
|
|
|
@ -228,6 +228,7 @@ Keep in mind that this function is not safe to use if you're sending exactly the
|
||||||
|
|
||||||
-- NOT SAFE
|
-- NOT SAFE
|
||||||
Cmd.batch [ sendOneEvent data , sendOneEvent data ]
|
Cmd.batch [ sendOneEvent data , sendOneEvent data ]
|
||||||
|
|
||||||
-}
|
-}
|
||||||
sendOneEvent : { content : D.Value, eventType : String, room : Room, stateKey : Maybe String, onResponse : VaultUpdate -> msg } -> Cmd msg
|
sendOneEvent : { content : D.Value, eventType : String, room : Room, stateKey : Maybe String, onResponse : VaultUpdate -> msg } -> Cmd msg
|
||||||
sendOneEvent =
|
sendOneEvent =
|
||||||
|
|
Loading…
Reference in New Issue