Last active
October 14, 2023 21:38
-
-
Save jkone27/0869b71b0aed614fbe5ea5a7fd39ecad to your computer and use it in GitHub Desktop.
F# Onion Architecture
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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