Skip to content

Instantly share code, notes, and snippets.

@jkone27
Last active October 14, 2023 21:38
Show Gist options
  • Save jkone27/0869b71b0aed614fbe5ea5a7fd39ecad to your computer and use it in GitHub Desktop.
Save jkone27/0869b71b0aed614fbe5ea5a7fd39ecad to your computer and use it in GitHub Desktop.
F# Onion Architecture
open System
open System.Threading.Tasks
module Domain =
type Order = { Number: string; Price: decimal; Type: string }
let changePrice order price =
{ order with Price = price }
module Infra = // Adapters (IO)
open Domain
type PriceRequestDto = { Type: string }
type PriceResponseDto = { NewPrice: decimal }
let getNewPriceFromApi (priceRequest: PriceRequestDto) = task {
do! Task.Delay(1000)
// we send price request DTO via HTTP to API
return { NewPrice = 7.0m }
}
let updateOrderPriceDb (order: Order) = task {
do! Task.Delay(1000)
printfn $"stored: %s{order.Number}"
}
module Services = //Ports, UseCases, Workflows
open Infra //dependnecy is just on DTO client infra
open Domain
type OrderChangeDto = { Number: string; Price: decimal; Type: string; Reason: string }
//mapper is part of services (Port/Adapter) connection
let private toDomain (orderChangeDto : OrderChangeDto) : Order =
{
Number = orderChangeDto.Number
Price = orderChangeDto.Price
Type = orderChangeDto.Type
}
let changeOrderPrice
(getApiPrice: PriceRequestDto -> Task<PriceResponseDto>)
(updateOrder: Order -> Task<unit>) orderDto =
task {
let order = orderDto |> toDomain
let priceRequest = { Type = orderDto.Type }
let! x = getApiPrice(priceRequest)
let newlyPricedOrder = changePrice order x.NewPrice
do! newlyPricedOrder |> updateOrder
}
module Mock =
open Services
open Infra
open Domain
let getRequest () =
{
Number = "TEST"
Price = 100.0m
Type = "Shoes"
Reason = "RepriceShoes"
}
let mockApiCall expected (dto: PriceRequestDto) = task {
printfn $"MOCK API call: {dto.Type}"
return { NewPrice = expected }
}
let MockDbCall (order: Order) = task {
printfn $"MOCK order store call: %s{order.Number}"
}
module App = //entry-point
open Services
// DI with partial application
let changeOrder =
Services.changeOrderPrice Infra.getNewPriceFromApi Infra.updateOrderPriceDb
let run args =
Mock.getRequest()
|> changeOrder
module Testing = //mocked dependencies
open Mock
open Services
// DI with partial application
let ``Test life is good with 3 $`` =
Services.changeOrderPrice (Mock.mockApiCall 3.0m) Mock.MockDbCall
let runTests () =
Mock.getRequest()
|> ``Test life is good with 3 $``
Testing.runTests ()
App.run ()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment