I have a code sample like this:
type Db =
| Db of Map<string, string>
let get id db =
let (Db dict) = db
dict
|> Map.tryFind id
|> (fun x -> x, db)
let add id value db =
let (Db dict) = db
dict
|> Map.add id value
|> Db
|> (fun x -> (), x)
let remove id db =
let (Db dict) = db
dict
|> Map.remove id
|> Db
|> (fun x -> (), x)
type GetByIdCapability =
| GetByIdCapability of ? (Id -> Value * ?) ?
type AddValueCapability =
| AddValueCapability of ? (Id -> Value -> ?) ?
type RemoveByIdCapability =
| RemoveByIdCapability of ? (Id -> ?) ?
type CreateAddValueCapability =
| CreateAddValueCapability of (Db -> AddValueCapability)
type AddValueService =
{
AddValueCapability: AddValueCapability
}
let createAddValueService db (CreateAddValueCapability createAddValueCapabilityFunc) =
let addValueCapability = db |> createAddValueCapabilityFunc
{
AddValueCapability = addValueCapability
}
let addValue id value (addValueService: AddValueService) =
addValueService.AddValueCapability ?
What I want is to have AddValueService which must have access only to "add value to Db" capability and has no access to other functions of Db inside service's logic. But at the same time I want to get updated instance of Db after calling service's logic. Is it possible to implement?
If I understand the question correctly, you can define your Db API in a separate module, and the capabilities like this:
Notice that the
AddValueCapabilitytype has aprivatecase constructor, which means that no code outside of the defining module can pattern-match on it.The
addValueCapabilityvalue is the only way external client code can get a hold of a value of that type. You may or may not want to export that value as well, depending on your exact needs.While external client code can't pattern-match on the
AddValueCapabilitycase constructor, it can run the capability using therunAddValuefunction.Here's a simple example of a function that is defined outside of the Db module:
It can run the capability, but not anything else if you also lock down the underlying functionality:
Here's an xUnit.net demo of it:
You can create other capabilities in a similar fashion, and pass only those capabilities around that you want client code to have...
...or you could just define some single-method interfaces and pass those around to client code. That's how you'd usually do it in OOD.