Как реализовать подход к возможностям БД с помощью чистых функций в F#?

У меня есть пример кода:

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 ?

Я хочу иметь AddValueService, который должен иметь доступ только к возможности «добавлять ценность в БД» и не имеет доступа к другим функциям БД внутри логики службы. Но в то же время я хочу получить обновленный экземпляр Db после вызова логики службы. Возможно ли реализовать?

Введение в одну из самых важных концепций в React - функциональное программирование
Введение в одну из самых важных концепций в React - функциональное программирование
React разработан с использованием концепции функционального программирования, поэтому понимание функционального программирования важно для изучения...
Фото ️🔁 Radek Jedynak 🔃 on ️🔁 Unsplash 🔃
Фото ️🔁 Radek Jedynak 🔃 on ️🔁 Unsplash 🔃
Что такое Java 8 Streams API? Java 8 Stream API
2
0
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если я правильно понимаю вопрос, вы можете определить свой Db API в отдельном модуле и такие возможности:

type AddValueCapability =
    private AddValueCapability of (string -> string -> Db -> unit * Db)

let addValueCapability = AddValueCapability add

let runAddValue (AddValueCapability f) = f

Обратите внимание, что тип AddValueCapability имеет конструктор case private, а это означает, что никакой код за пределами определяющего модуля не может сопоставляться с ним по образцу.

Значение addValueCapability — это единственный способ, которым внешний клиентский код может получить значение этого типа. Вы можете экспортировать или не экспортировать это значение, в зависимости от ваших конкретных потребностей.

Хотя внешний клиентский код не может выполнять сопоставление с шаблоном в конструкторе case AddValueCapability, он может запускать эту возможность с помощью функции runAddValue.

Вот простой пример функции, которая определена вне модуля Db:

let useCapability (cap : AddValueCapability) db =
    runAddValue cap "foo" "bar" db

Он может запускать эту возможность, но не что-либо еще, если вы также заблокируете базовую функциональность:

let private get id db =   
    let (Db dict) = db
    dict
    |> Map.tryFind id
    |> (fun x -> x, db)
    
let private add id value db =
    let (Db dict) = db
    dict
    |> Map.add id value
    |> Db
    |> (fun x -> (), x)

let private remove id db =
    let (Db dict) = db
    dict
    |> Map.remove id
    |> Db
    |> (fun x -> (), x)

Вот демонстрация xUnit.net:

[<Fact>]
let Demo () =
    let db = Map.empty |> Db
    let _, actual = useCapability addValueCapability db
    Assert.Equal (Map.ofList ["foo", "bar"] |> Db, actual)

Вы можете создавать другие возможности аналогичным образом и передавать только те возможности, которые вы хотите, чтобы клиентский код имел...

... или вы можете просто определить некоторые интерфейсы с одним методом и передать их клиентскому коду. Вот как вы обычно делаете это в OOD.

Другие вопросы по теме