jim108dev
jim108dev

Reputation: 101

Shared domain types and single responsibility principle, how do they work together?

I think my problem is best explained on a hypothetical example.

Suppose you want to design a software for several banks. Two use cases are: calculating a credit score and evaluating taxes. The score does not depend on the taxes and vice versa.

The return type of the credit scoring function is Score and the taxes are returned in TaxAmount. You want the solution as clean and extensible as possible.

Where would you put the shared/not so shared types?

  1. Putting Score and TaxAmount in one shared type module would violate the single responsibility principle.
  2. Putting Score and TaxAmountin different modules increases the number of files/modules/git submodules very quickly as the code base grows. And the problem spreads through all layers higher up which results in a lot of open module statements.
  3. Having each function its own type module results in repetition.
  4. Not using custom domain types results in losing the benefits of custom domain types.

My constructed onion architecture example:

namespace Domain.Shared

module Types =

    type Score = 
    | GOOD
    | BAD

    type TaxAmount = int


namespace Domain.Scoring1

module Main =
    open Domain.Shared.Types

    let go input :Score = Score.BAD


namespace Domain.Scoring2

module Main =
    open Domain.Shared.Types

    let go input :Score = Score.GOOD


namespace Domain.ComputeTaxes1

module Main =
    open Domain.Shared.Types

    let go input :TaxAmount = 1


namespace Application

module BankA =

    let standardize x = "a"

    let score input = 
        
        input
        |> standardize
        |> Domain.Scoring1.Main.go

    let computeTaxes input = 
        
        input
        |> standardize
        |> Domain.ComputeTaxes1.Main.go


module BankB =

    let standardize x = "b"

    let score input = 
        
        input
        |> standardize
        |> Domain.Scoring2.Main.go


namespace Infrastructure

module BankA =

    let read filename = "A"

    let score filename = 
        
        filename
        |> read
        |> Application.BankA.score

    let computeTaxes (filename:string) = 
        
        filename
        |> read
        |> Application.BankA.computeTaxes

module BankB =

    let read filename = "B"

    let score (filename:string) = 
        
        filename
        |> read
        |> Application.BankB.score

module Shell =

    [<EntryPoint>]
    let main (args: array<string>) =
        match args with
        | [| "A"; "score"; filename |] -> printfn "%A" (BankA.score filename)
        | [| "A"; "tax"; filename |] -> printfn "%A" (BankA.computeTaxes filename)
        | [| "B"; "score"; filename |] -> printfn "%A" (BankB.score filename)
        | _ -> printfn "Not implemented"
        0

Upvotes: 0

Views: 154

Answers (0)

Related Questions