├── .gitignore ├── 032-intro ├── README.md └── main.go ├── 065-sum-types ├── main.fsx └── main.go ├── 067-sum-types ├── main.fsx └── main.go ├── 068-composing-types ├── main.fsx └── main.go ├── 079-modeling-simple-values ├── main.fsx └── main.go ├── 084-undefined-type ├── main.fsx └── main.go ├── 085-workflows-struct └── main.go ├── 086-workflows-sum-switch value └── main.go ├── 088-value-objects ├── README.md ├── go.mod ├── main.go └── valueobject001 │ └── immutable.go ├── 089-entity-identifiers └── main.go ├── 091-entities-inside-id └── main.go ├── 092-entities-inside-id-equality └── main.go ├── 107-units-of-measure └── main.go ├── 108-invariant └── main.go ├── 108-invariant2 └── main.go ├── 109-invariant3 └── main.go ├── 110-capturing-rules ├── go.mod ├── main.go └── verificationservice │ └── verificationservice.go ├── 111-sumtypes └── main.go ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | MISC/ 2 | concurrency-book/ 3 | .DS_Store 4 | WORKING/ 5 | .ionide 6 | 7 | .fake -------------------------------------------------------------------------------- /032-intro/README.md: -------------------------------------------------------------------------------- 1 | # pg.32 2 | 3 | - Data structure of Place Order workflow 4 | 5 | - Original F# pseudo-code: 6 | ``` 7 | bounded context: Order-Taking 8 | data Order = 9 | CustomerInfo 10 | AND ShippingAddress 11 | AND BillingAddress 12 | AND list of OrderLines 13 | AND AmountToBill 14 | data OrderLine = 15 | Product 16 | AND Quantity 17 | AND Price 18 | data CustomerInfo = ??? 19 | data BillingAddress = ??? // don't know yet 20 | ``` 21 | -------------------------------------------------------------------------------- /032-intro/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Order interface { 8 | Val() Order 9 | } 10 | 11 | type OrderLine struct { 12 | Product string 13 | Quantity int 14 | Price float32 15 | } 16 | 17 | type CustomerInfo struct{ LastName string } 18 | type ShippingAddress struct{ Address string } 19 | type BillingAddress struct{ Address string } 20 | type OrderLines struct{ OrderLine } 21 | type OrderArray []OrderLine 22 | type AmountToBill struct{ Cost float32 } 23 | 24 | type Order2 struct { 25 | CustomerInfo 26 | ShippingAddress 27 | BillingAddress 28 | OrderArray 29 | AmountToBill 30 | } 31 | 32 | // Implement method for type-checking 33 | func (c CustomerInfo) Val() Order { return c } 34 | func (s ShippingAddress) Val() Order { return s } 35 | func (b BillingAddress) Val() Order { return b } 36 | func (o OrderLines) Val() Order { return o } 37 | func (a AmountToBill) Val() Order { return a } 38 | 39 | var ( 40 | _ Order = (*CustomerInfo)(nil) 41 | _ Order = (*ShippingAddress)(nil) 42 | _ Order = (*BillingAddress)(nil) 43 | _ Order = (*OrderLines)(nil) 44 | _ Order = (*AmountToBill)(nil) 45 | ) 46 | 47 | func do(i Order) string { 48 | switch v := i.(type) { 49 | case CustomerInfo: 50 | return fmt.Sprintf("type CustomerInfo: %v", v) 51 | case ShippingAddress: 52 | return fmt.Sprintf("type ShippingAddress: %v", v) 53 | case BillingAddress: 54 | return fmt.Sprintf("type BillingAddress: %v", v) 55 | case OrderLines: 56 | return fmt.Sprintf("type OrderLines: %v", v) 57 | case AmountToBill: 58 | return fmt.Sprintf("type AmountToBill: %v", v) 59 | default: 60 | return fmt.Sprintf("Unexpected type: %v", v) 61 | } 62 | } 63 | 64 | func main() { 65 | var orderArray OrderArray 66 | 67 | customerInfo := CustomerInfo{LastName: "Baptista"} 68 | shippingAddess := ShippingAddress{Address: "123 Yuzawa"} 69 | billingAddress := BillingAddress{Address: "123 Yuzawa"} 70 | 71 | orderArray = []OrderLine{ 72 | OrderLine{Product: "TV", Quantity: 343, Price: 33.34}, 73 | OrderLine{Product: "Bagel", Quantity: 1003, Price: 2.13}, 74 | OrderLine{Product: "Bike", Quantity: 3, Price: 443.55}, 75 | } 76 | 77 | orderArray2 := OrderArray{OrderLine{Product: "TV", Quantity: 343, Price: 33.34}, 78 | OrderLine{Product: "Bagel", Quantity: 1003, Price: 2.13}, 79 | OrderLine{Product: "Bike", Quantity: 3, Price: 443.55}} 80 | 81 | fmt.Printf("1. orderArray = %T\n", orderArray) 82 | fmt.Printf("2. orderArray = %#v\n", orderArray) 83 | fmt.Println("--------------------") 84 | fmt.Printf("1. orderArray = %T\n", orderArray2) 85 | fmt.Printf("2. orderArray = %#v\n", orderArray2) 86 | fmt.Println("--------------------") 87 | fmt.Println(do(customerInfo)) 88 | fmt.Println(do(shippingAddess)) 89 | fmt.Println(do(billingAddress)) 90 | } 91 | -------------------------------------------------------------------------------- /065-sum-types/main.fsx: -------------------------------------------------------------------------------- 1 | module Example065 = 2 | 3 | type AppleVariety = 4 | | GoldenDelicious 5 | | GrannySmith 6 | | Fuji 7 | 8 | type BananaVariety = 9 | | Cavendish 10 | | GrosMichel 11 | | Manzano 12 | 13 | type CherryVariety = 14 | | Montmorency 15 | | Bing -------------------------------------------------------------------------------- /065-sum-types/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type AppleVariety interface { 8 | isAppleVariety() 9 | } 10 | 11 | type GoldenDelicious string 12 | type GrannySmith string 13 | type Fuji string 14 | 15 | func (g GoldenDelicious) isAppleVariety() {} 16 | func (gr GrannySmith) isAppleVariety() {} 17 | func (f Fuji) isAppleVariety() {} 18 | 19 | var ( 20 | _ AppleVariety = (*GoldenDelicious)(nil) 21 | _ AppleVariety = (*GrannySmith)(nil) 22 | _ AppleVariety = (*Fuji)(nil) 23 | ) 24 | 25 | //------------------------------------------------ 26 | 27 | type BananaVariety interface { 28 | isBananaVariety() 29 | } 30 | 31 | type Cavendish string 32 | type GrosMichel string 33 | type Manzano string 34 | 35 | func (Cavendish) isBananaVariety() {} 36 | func (GrosMichel) isBananaVariety() {} 37 | func (Manzano) isBananaVariety() {} 38 | 39 | var ( 40 | _ BananaVariety = (*Cavendish)(nil) 41 | _ BananaVariety = (*GrosMichel)(nil) 42 | _ BananaVariety = (*Manzano)(nil) 43 | ) 44 | 45 | //------------------------------------------------ 46 | 47 | type CherryVariety interface { 48 | isCherryVariety() 49 | } 50 | 51 | type Montmorency string 52 | type Bing string 53 | 54 | func (Montmorency) isCherryVariety() {} 55 | func (Bing) isCherryVariety() {} 56 | 57 | var ( 58 | _ CherryVariety = (*Montmorency)(nil) 59 | _ CherryVariety = (*Bing)(nil) 60 | ) 61 | 62 | //================================================ 63 | 64 | type FruitSnack interface { 65 | isFruitSnack() 66 | } 67 | 68 | type Apple string 69 | type Banana string 70 | type Cherries string 71 | 72 | func (a Apple) isAppleVariety() {} 73 | func (a Apple) isFruitSnack() {} 74 | 75 | func (b Banana) isBananaVariety() {} 76 | func (a Banana) isFruitSnack() {} 77 | 78 | func (c Cherries) isCherryVariety() {} 79 | func (c Cherries) isFruitSnack() {} 80 | 81 | var ( 82 | _ FruitSnack = (*Apple)(nil) 83 | _ FruitSnack = (*Banana)(nil) 84 | _ FruitSnack = (*Cherries)(nil) 85 | ) 86 | 87 | func chooseFruitSnank(i FruitSnack) { 88 | switch i.(type) { 89 | case Apple: 90 | fmt.Println("SELECT: Apple") 91 | case Banana: 92 | fmt.Println("SELECT: Banana") 93 | case Cherries: 94 | fmt.Println("SELECT: Cherries") 95 | } 96 | } 97 | 98 | //------------------------------- 99 | 100 | func main() { 101 | fmt.Println("------------------------------") 102 | food := Apple("apple") 103 | chooseFruitSnank(food) 104 | 105 | } 106 | -------------------------------------------------------------------------------- /067-sum-types/main.fsx: -------------------------------------------------------------------------------- 1 | module Example067 = 2 | 3 | type OrderQuantity = 4 | | UnitQuantity of int 5 | | KilogramQuantity of float 6 | 7 | let anOrderQtyInUnits = UnitQuantity 10 8 | let anOrderQtyInKg = KilogramQuantity 2.5 9 | 10 | let printQuantity aOrderQty = 11 | match aOrderQty with 12 | | UnitQuantity uQty -> 13 | printfn "%i units" uQty 14 | | KilogramQuantity kgQty -> 15 | printfn "%g kg" kgQty 16 | 17 | printQuantity anOrderQtyInUnits // "10 units" 18 | printQuantity anOrderQtyInKg // "2.5 kg" -------------------------------------------------------------------------------- /067-sum-types/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type OrderQuantity interface { 8 | isOrderQuantity() 9 | } 10 | 11 | type UnitQuantity int 12 | type KilogramQuantity float32 13 | 14 | func (u UnitQuantity) isOrderQuantity() {} 15 | func (k KilogramQuantity) isOrderQuantity() {} 16 | 17 | var ( 18 | _ OrderQuantity = (*UnitQuantity)(nil) 19 | _ OrderQuantity = (*KilogramQuantity)(nil) 20 | ) 21 | 22 | func printQuantity(i OrderQuantity) { 23 | switch i.(type) { 24 | case UnitQuantity: 25 | fmt.Printf("%d units\n", i) // base 10 integer 26 | case KilogramQuantity: 27 | fmt.Printf("%1.2f kg\n", i) // float, width 1, precision 2 28 | } 29 | } 30 | 31 | //---------------------------------------- 32 | 33 | func main() { 34 | anOrderQtyInUnits := UnitQuantity(10) 35 | anOrderQtyInKg := KilogramQuantity(2.5) 36 | 37 | printQuantity(anOrderQtyInUnits) 38 | printQuantity(anOrderQtyInKg) 39 | } 40 | -------------------------------------------------------------------------------- /068-composing-types/main.fsx: -------------------------------------------------------------------------------- 1 | module Example068 = 2 | 3 | type CheckNumber = CheckNumber of int 4 | type CardNumber = CardNumber of string 5 | 6 | // 'OR' type 7 | type CardType = 8 | | Visa 9 | | Mastercard 10 | 11 | // 'AND' type (record) 12 | type CreditCardInfo = { 13 | CardType : CardType 14 | CardNumber : CardNumber 15 | } 16 | 17 | type PaymentMethod = 18 | | Cash 19 | | Check of CheckNumber 20 | | Card of CreditCardInfo 21 | 22 | type PaymentAmount = PaymentAmount of decimal 23 | type Currency = EUR | USD 24 | 25 | // top-level type 26 | type Payment = { 27 | Amount : PaymentAmount 28 | Currency: Currency 29 | Method: PaymentMethod 30 | } 31 | 32 | type ConvertPaymentCurrency = 33 | Payment -> Currency -> Payment -------------------------------------------------------------------------------- /068-composing-types/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "unsafe" 6 | ) 7 | 8 | //---------------------------------------------------- 9 | 10 | type CheckNumber int 11 | type CardNumber string 12 | 13 | //---------------------------------------------------- 14 | 15 | type CardType interface { 16 | isCardType() 17 | } 18 | 19 | type Visa string 20 | type Mastercard string 21 | 22 | func (Visa) isCardType() {} 23 | func (Mastercard) isCardType() {} 24 | 25 | var ( 26 | _ CardType = (*Visa)(nil) 27 | _ CardType = (*Mastercard)(nil) 28 | ) 29 | 30 | //---------------------------------------------------- 31 | 32 | type CreditCardInfo struct { 33 | CardType 34 | CardNumber 35 | } 36 | 37 | //---------------------------------------------------- 38 | 39 | // This is no longer a simple “enum” because some of the choices have 40 | // data associated with them: the Check case has a CheckNumber and the 41 | // Card case has CreditCardInfo. 42 | 43 | type PaymentMethod interface { 44 | isPaymentMethod() 45 | } 46 | type Cash string 47 | type Check CheckNumber 48 | type Card CreditCardInfo 49 | 50 | func (c Cash) isPaymentMethod() {} 51 | func (c Check) isPaymentMethod() {} 52 | func (c Card) isPaymentMethod() {} 53 | 54 | var ( 55 | _ PaymentMethod = (*Cash)(nil) 56 | _ PaymentMethod = (*Check)(nil) 57 | _ PaymentMethod = (*Card)(nil) 58 | ) 59 | 60 | //---------------------------------------------------- 61 | 62 | type PaymentAmount float32 63 | 64 | type Currency interface { 65 | isCurrency() 66 | } 67 | type EUR string 68 | type USD string 69 | 70 | func (e EUR) isCurrency() {} 71 | func (u USD) isCurrency() {} 72 | 73 | var ( 74 | _ Currency = (*EUR)(nil) 75 | _ Currency = (*USD)(nil) 76 | ) 77 | 78 | //---------------------------------------------------- 79 | 80 | // Payment is the top-level type. 81 | type Payment struct { 82 | Amount PaymentAmount 83 | Currency 84 | Method PaymentMethod 85 | } 86 | 87 | //---------------------------------------------------- 88 | 89 | // Given an UnpaidInvoice and then a Payment, we can create a PaidInvoice. 90 | type UnpaidInvoice float64 91 | type PaidInvoice float64 92 | 93 | type PayInvoiceType func(a UnpaidInvoice, p Payment) PaidInvoice 94 | 95 | var PayInvoice PayInvoiceType = func(a UnpaidInvoice, p Payment) PaidInvoice { 96 | fmt.Printf("a: %T\n", a) 97 | fmt.Printf("p: %T\n", p.Amount) 98 | paidInvoice := PaidInvoice(a) 99 | return paidInvoice 100 | } 101 | 102 | type Test func(a, b int) int 103 | 104 | var TestFunc Test = func(a, b int) int { 105 | return a + b 106 | } 107 | 108 | //---------------------------------------------------- 109 | 110 | type ConvertPaymentCurrencyType func(p Payment, target Currency) Payment 111 | 112 | var ConvertPaymentCurrency = func() { 113 | var payment Payment 114 | fmt.Printf("PAYMENT %#v\n", payment) 115 | } 116 | 117 | //---------------------------------------------------- 118 | 119 | func main() { 120 | 121 | var checknumber CheckNumber 122 | fmt.Printf("%T, %v bytes\n", checknumber, unsafe.Sizeof(checknumber)) 123 | 124 | var cardnumber CardNumber 125 | fmt.Printf("%T, %v bytes\n", cardnumber, unsafe.Sizeof(cardnumber)) 126 | 127 | //---------- 128 | 129 | var cardtype CardType 130 | fmt.Printf("%T, %v bytes\n", cardtype, unsafe.Sizeof(cardtype)) 131 | 132 | var visa Visa 133 | fmt.Printf("%T, %v bytes\n", visa, unsafe.Sizeof(visa)) 134 | 135 | var mastercard Mastercard 136 | fmt.Printf("%T, %v bytes\n", mastercard, unsafe.Sizeof(mastercard)) 137 | 138 | //---------- 139 | 140 | var creditcardinfo CreditCardInfo 141 | fmt.Printf("%T, %v bytes\n", creditcardinfo, unsafe.Sizeof(creditcardinfo)) 142 | 143 | //---------- 144 | 145 | var paymentmethod PaymentMethod 146 | fmt.Printf("%T, %v bytes\n", paymentmethod, unsafe.Sizeof(paymentmethod)) 147 | var cash Cash 148 | fmt.Printf("%T, %v bytes\n", cash, unsafe.Sizeof(cash)) 149 | var check Check 150 | fmt.Printf("%T, %v bytes\n", check, unsafe.Sizeof(check)) 151 | var card Card 152 | fmt.Printf("%T, %v bytes\n", card, unsafe.Sizeof(card)) 153 | 154 | //---------- 155 | 156 | var paymentamount PaymentAmount 157 | fmt.Printf("%T, %v bytes\n", paymentamount, unsafe.Sizeof(paymentamount)) 158 | var currency Currency 159 | fmt.Printf("%T, %v bytes\n", currency, unsafe.Sizeof(currency)) 160 | var eur EUR 161 | fmt.Printf("%T, %v bytes\n", eur, unsafe.Sizeof(eur)) 162 | var usd USD 163 | fmt.Printf("%T, %v bytes\n", usd, unsafe.Sizeof(usd)) 164 | 165 | //---------- 166 | 167 | var payment Payment 168 | fmt.Printf("%T, %v bytes\n", payment, unsafe.Sizeof(payment)) 169 | 170 | //---------- 171 | 172 | var unpaidinvoice UnpaidInvoice 173 | fmt.Printf("%T, %v bytes\n", unpaidinvoice, unsafe.Sizeof(unpaidinvoice)) 174 | var paidinvoice PaidInvoice 175 | fmt.Printf("%T, %v bytes\n", paidinvoice, unsafe.Sizeof(paidinvoice)) 176 | var payinvoicetype PayInvoiceType 177 | fmt.Printf("%T, %v bytes\n", payinvoicetype, unsafe.Sizeof(payinvoicetype)) 178 | 179 | fmt.Printf("%T, %v bytes\n", PayInvoice, unsafe.Sizeof(PayInvoice)) 180 | 181 | //---------- 182 | 183 | ConvertPaymentCurrency() 184 | fmt.Println(TestFunc(1, 2)) 185 | 186 | var amount Payment 187 | amount = Payment{Amount: 34.3} 188 | 189 | fmt.Printf("%#v\n", amount) 190 | fmt.Println(PayInvoice(1000.0, amount)) 191 | } 192 | -------------------------------------------------------------------------------- /079-modeling-simple-values/main.fsx: -------------------------------------------------------------------------------- 1 | module Example079 = 2 | 3 | type CustomerId = 4 | | CustomerId of int 5 | 6 | type WidgetCode = WidgetCode of string 7 | type UnitQuantity = UnitQuantity of int 8 | type KilogramQuantity = KilogramQuantity of decimal 9 | 10 | 11 | // Deconstruct a single case union 12 | // construct 13 | let customerId = CustomerId 42 14 | 15 | // deconstruct 16 | let (CustomerId innerValue) = customerId 17 | printfn "%i" innerValue 18 | 19 | // deconstruct directly in the parameter of a function definition 20 | let processCustomerId (CustomerId innerValue) = 21 | printfn "innerValue is %i" innerValue 22 | 23 | processCustomerId customerId -------------------------------------------------------------------------------- /079-modeling-simple-values/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // This is a reference type 8 | type CustomerID struct { 9 | float64 10 | } 11 | 12 | // This is a value type, but using it as a pointer prevents casting back to float64 13 | type CustomerID2 float64 14 | type CustomerID3 float64 15 | 16 | // Go does not permit casting a pointer to another pointer 17 | // This is how we can prevent passing simple float64 into here 18 | func TestReference(input *CustomerID2) CustomerID2 { 19 | fmt.Printf("input is %T\n", input) 20 | return *input 21 | } 22 | 23 | func TestStruct(input CustomerID) CustomerID { 24 | fmt.Printf("input is %#v\n", input) 25 | return input 26 | } 27 | 28 | func main() { 29 | customerid2 := CustomerID2(33.33) 30 | fmt.Println(TestReference(&customerid2)) 31 | 32 | customerid2b := CustomerID2(133.33) 33 | fmt.Println(customerid2 == customerid2b) 34 | 35 | c := CustomerID{3343.343} 36 | fmt.Println(TestStruct(c)) 37 | fmt.Printf("%+v", TestStruct(c)) 38 | } 39 | -------------------------------------------------------------------------------- /084-undefined-type/main.fsx: -------------------------------------------------------------------------------- 1 | module Example084 = 2 | 3 | type Undefined = exn 4 | 5 | type CustomerInfo = Undefined 6 | type ShippingAddress = Undefined 7 | type BillingAddress = Undefined 8 | type OrderLine = Undefined 9 | type BillingAmount = Undefined 10 | 11 | type Order = { 12 | CustomerInfo : CustomerInfo 13 | ShippingAddress : ShippingAddress 14 | BillingAddress : BillingAddress 15 | OrderLines : OrderLine list 16 | AmountToBill : BillingAmount 17 | } -------------------------------------------------------------------------------- /084-undefined-type/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type CustomerInfo struct{} 8 | type ShippingAddress struct{} 9 | type BillingAddress struct{} 10 | type OrderLine struct{} 11 | type BillingAmount struct{} 12 | 13 | type Order struct { 14 | CustomerInfo 15 | ShippingAddress 16 | BillingAddress 17 | OrderLine 18 | BillingAmount 19 | } 20 | 21 | func main() { 22 | var order Order 23 | fmt.Printf("%#v\n", order) 24 | } 25 | -------------------------------------------------------------------------------- /085-workflows-struct/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type AcknowledgmentSent interface{} 8 | type OrderPlaced interface{} 9 | type BillableOrderPlaced interface{} 10 | type UnvalidatedOrder interface{} 11 | 12 | type PlaceOrderEvents struct { 13 | AcknowledgmentSent 14 | OrderPlaced 15 | BillableOrderPlaced 16 | } 17 | 18 | type PlaceOrder func(UnvalidatedOrder) PlaceOrderEvents 19 | 20 | var placeOrder PlaceOrder = func(u UnvalidatedOrder) PlaceOrderEvents { 21 | result := PlaceOrderEvents{ 22 | AcknowledgmentSent: 1, 23 | OrderPlaced: u, 24 | BillableOrderPlaced: 1.2, 25 | } 26 | return result 27 | } 28 | 29 | func main() { 30 | fmt.Printf("%+v\n", placeOrder("Pizza")) 31 | } 32 | -------------------------------------------------------------------------------- /086-workflows-sum-switch value/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type EnvelopeContents string 8 | 9 | type CategorizedMail interface { 10 | isCategorizedMail() 11 | } 12 | 13 | type QuoteForm struct{ quantity float64 } 14 | type OrderForm struct{ quantity float64 } 15 | 16 | func (q QuoteForm) isCategorizedMail() {} 17 | func (o OrderForm) isCategorizedMail() {} 18 | 19 | var ( 20 | c1 CategorizedMail = (*QuoteForm)(nil) 21 | c2 CategorizedMail = (*OrderForm)(nil) 22 | ) 23 | 24 | func chooseCategorizedMail(c CategorizedMail) float64 { 25 | switch value := c.(type) { 26 | case QuoteForm: 27 | fmt.Printf("%#v\n", value) 28 | fmt.Printf("%#v\n", value.quantity*2) 29 | return value.quantity 30 | case OrderForm: 31 | fmt.Printf("%#v\n", value.quantity*10.1) 32 | return value.quantity 33 | default: 34 | panic(fmt.Sprintf("PANIC: %v", c)) 35 | } 36 | } 37 | 38 | type Test struct{} 39 | 40 | //---------------------------------------------- 41 | type ProductCatalog struct{ price float64 } 42 | type PricedOrder float64 43 | 44 | type CalculatePrices func(OrderForm, ProductCatalog) PricedOrder 45 | 46 | var calculatePrices CalculatePrices = func(o OrderForm, p ProductCatalog) PricedOrder { 47 | total := o.quantity * p.price 48 | pricedorder := PricedOrder(total) 49 | return pricedorder 50 | } 51 | 52 | //---------------------------------------------- 53 | 54 | func main() { 55 | fmt.Println("----------------------------") 56 | 57 | category := OrderForm{quantity: 29.54} 58 | fmt.Printf("%#v\n", chooseCategorizedMail(category)) 59 | 60 | category2 := QuoteForm{quantity: 1000.97} 61 | fmt.Printf("%#v\n", chooseCategorizedMail(category2)) 62 | 63 | o := OrderForm{quantity: 99.3} 64 | p := ProductCatalog{price: 45.3} 65 | fmt.Println("calculatePrices: ", calculatePrices(o, p)) 66 | 67 | // test := Test{} 68 | // chooseCategorizedMail(Test) 69 | } 70 | -------------------------------------------------------------------------------- /088-value-objects/README.md: -------------------------------------------------------------------------------- 1 | # Immutable object in Go 2 | 3 | - Pseudo-immutability in Go 4 | 1. Create struct in specialized package 5 | 2. Enable struct equality 6 | 3. Blocks mutability 7 | -------------------------------------------------------------------------------- /088-value-objects/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/gbih/ddd-in-go/088-value-objects/immutable 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /088-value-objects/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | import vo "github.com/gbih/ddd-in-go/088-value-objects/immutable/valueobject001" 5 | 6 | func main() { 7 | // Record types 8 | fmt.Printf("%#v\n", vo.Name1) 9 | fmt.Println(vo.Name1 == vo.Name2) 10 | fmt.Println(vo.Name1 == vo.Name3) 11 | 12 | // This mutation is not possible: 13 | // vo.Name1.firstname = "Michael" 14 | 15 | fmt.Println("------------") 16 | fmt.Println(vo.Address1 == vo.Address2) 17 | 18 | fmt.Println("------------") 19 | fmt.Println(vo.Phone1) 20 | fmt.Println(vo.Phone1 == vo.Phone2) 21 | fmt.Println(vo.Phone1 == vo.Phone3) 22 | 23 | // Choice types (sum types) 24 | // cannot compare different types 25 | // fmt.Println(vo.Phone1 == vo.Order4) 26 | } 27 | -------------------------------------------------------------------------------- /088-value-objects/valueobject001/immutable.go: -------------------------------------------------------------------------------- 1 | package valueobject001 2 | 3 | type name struct { 4 | firstname string 5 | lastname string 6 | } 7 | 8 | type address struct { 9 | streetaddress string 10 | city string 11 | zip string 12 | } 13 | 14 | var Name1 = name{ 15 | firstname: "Alex", 16 | lastname: "Adams", 17 | } 18 | 19 | var Name2 = name{ 20 | firstname: "Alex", 21 | lastname: "Adams", 22 | } 23 | 24 | var Name3 = name{ 25 | firstname: "John", 26 | lastname: "Smith", 27 | } 28 | 29 | var Address1 = address{ 30 | streetaddress: "123 Main St", 31 | city: "New York", 32 | zip: "90001", 33 | } 34 | 35 | var Address2 = address{ 36 | streetaddress: "123 Main St", 37 | city: "New York", 38 | zip: "90001", 39 | } 40 | 41 | type phone interface { 42 | isphone() 43 | } 44 | type phoneCompany struct{ model string } 45 | type phonePersonal struct{ model string } 46 | 47 | func (o phoneCompany) isphone() {} 48 | func (o phonePersonal) isphone() {} 49 | 50 | var ( 51 | _ phone = (*phoneCompany)(nil) 52 | _ phone = (*phonePersonal)(nil) 53 | ) 54 | 55 | var Phone1 = phoneCompany{model: "AAA"} 56 | var Phone2 = phoneCompany{model: "AAA"} 57 | var Phone3 = phoneCompany{model: "BBB"} 58 | var Phone4 = phonePersonal{model: "BBB"} 59 | -------------------------------------------------------------------------------- /089-entity-identifiers/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | guuid "github.com/google/uuid" 6 | ) 7 | 8 | type ContactId interface{} 9 | type PhoneNumber int 10 | type EmailAddress int 11 | 12 | type Contact struct { 13 | ContactId 14 | PhoneNumber 15 | EmailAddress 16 | } 17 | 18 | func genUUID() { 19 | id := guuid.New() 20 | fmt.Printf("github.com/google/uuid: %s\n", id.String()) 21 | } 22 | 23 | func generateContacts() interface{} { 24 | var sum []Contact 25 | var contact Contact 26 | 27 | for i := 1; i < 5; i++ { 28 | contact = Contact{ 29 | ContactId(guuid.New()), 30 | PhoneNumber(i), 31 | EmailAddress(i), 32 | } 33 | sum = append(sum, contact) 34 | } 35 | return sum 36 | } 37 | 38 | func main() { 39 | fmt.Printf("%+v\n", generateContacts()) 40 | } 41 | -------------------------------------------------------------------------------- /091-entities-inside-id/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | It’s more common to store the ID using the “inside” approach, 3 | where each case has a copy of the identifier. Applied here, 4 | we would create two separate types, one for each case 5 | (UnpaidInvoice and PaidInvoice), both of which have their own 6 | InvoiceId, and then a top-level Invoice type, which is a choice 7 | between them. 8 | */ 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | uuid "github.com/google/uuid" 14 | ) 15 | 16 | type InvoiceId interface{} 17 | 18 | type Invoice interface { 19 | isInvoice() 20 | } 21 | 22 | type UnpaidInvoice struct { 23 | InvoiceId 24 | } 25 | 26 | type PaidInvoice struct { 27 | InvoiceId 28 | } 29 | 30 | func (u UnpaidInvoice) isInvoice() {} 31 | func (p PaidInvoice) isInvoice() {} 32 | 33 | var ( 34 | _ Invoice = (*UnpaidInvoice)(nil) 35 | _ Invoice = (*PaidInvoice)(nil) 36 | ) 37 | 38 | // The benefit of this approach is that now, when we do our 39 | // pattern matching, we have all the data accessible in one place, 40 | // including the ID: 41 | func chooseInvoice(i Invoice) { 42 | switch value := i.(type) { 43 | case UnpaidInvoice: 44 | fmt.Printf("The unpaid invoiceid is %v\n", value.InvoiceId) 45 | case PaidInvoice: 46 | fmt.Printf("The paid invoiceis is %v\n", value.InvoiceId) 47 | default: 48 | panic("Wrong data") 49 | } 50 | } 51 | 52 | func main() { 53 | 54 | fmt.Println("-----------------------------") 55 | a := PaidInvoice{InvoiceId: InvoiceId(uuid.New())} 56 | chooseInvoice(a) 57 | 58 | b := UnpaidInvoice{InvoiceId: InvoiceId(uuid.New())} 59 | chooseInvoice(b) 60 | 61 | // This is rejected: 62 | // chooseInvoice(322342342342343233234) 63 | } 64 | -------------------------------------------------------------------------------- /092-entities-inside-id-equality/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | It’s more common to store the ID using the “inside” approach, 3 | where each case has a copy of the identifier. Applied here, 4 | we would create two separate types, one for each case 5 | (UnpaidInvoice and PaidInvoice), both of which have their own 6 | InvoiceId, and then a top-level Invoice type, which is a choice 7 | between them. 8 | */ 9 | package main 10 | 11 | import ( 12 | "fmt" 13 | uuid "github.com/google/uuid" 14 | ) 15 | 16 | type InvoiceId interface{} 17 | 18 | type Invoice interface { 19 | isInvoice() 20 | val() Invoice 21 | } 22 | 23 | type UnpaidInvoice struct { 24 | InvoiceId 25 | } 26 | 27 | type PaidInvoice struct { 28 | InvoiceId 29 | } 30 | 31 | func (u UnpaidInvoice) isInvoice() {} 32 | func (u UnpaidInvoice) val() Invoice { return u } 33 | func (p PaidInvoice) isInvoice() {} 34 | func (p PaidInvoice) val() Invoice { return p } 35 | 36 | var ( 37 | _ Invoice = (*UnpaidInvoice)(nil) 38 | _ Invoice = (*PaidInvoice)(nil) 39 | // alternative 40 | _ Invoice = &UnpaidInvoice{} 41 | _ Invoice = &PaidInvoice{} 42 | ) 43 | 44 | // The benefit of this approach is that now, when we do our 45 | // pattern matching, we have all the data accessible in one place, 46 | // including the ID: 47 | func chooseInvoice(i Invoice) { 48 | switch value := i.(type) { 49 | case UnpaidInvoice: 50 | fmt.Printf("The unpaid invoiceid is %v\n", value.InvoiceId) 51 | case PaidInvoice: 52 | fmt.Printf("The paid invoice is is %v\n", value.InvoiceId) 53 | default: 54 | panic("Wrong data") 55 | } 56 | } 57 | 58 | func main() { 59 | fmt.Println("-----------------------------") 60 | empty := PaidInvoice{} 61 | 62 | fmt.Printf("EMPTY PaidInvoice: %#v\n", empty) 63 | 64 | a := PaidInvoice{InvoiceId: InvoiceId(uuid.New())} 65 | fmt.Printf("REGULAR PaidInvoice: %#v\n", a) 66 | fmt.Println() 67 | 68 | chooseInvoice(a) 69 | 70 | b := UnpaidInvoice{InvoiceId: InvoiceId(uuid.New())} 71 | chooseInvoice(b) 72 | 73 | fmt.Println(a.val() == b.val()) 74 | // This is rejected: 75 | // chooseInvoice(322342342342343233234) 76 | } 77 | -------------------------------------------------------------------------------- /107-units-of-measure/main.go: -------------------------------------------------------------------------------- 1 | // standardized unit types to maintain compatibility 2 | // Microsoft.FSharp.Data.UnitSystems.SI 3 | // https://github.com/martinlindhe/unit 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | // Length represents a SI unit of length (in meters, m) 12 | type Length Unit 13 | 14 | type dpi Length 15 | type inch Length 16 | type px Length 17 | 18 | var dpiValue dpi = dpi(150) 19 | 20 | // Unit represents a unit 21 | type Unit float64 22 | 23 | // Interesting approach: 24 | // https://github.com/martinlindhe/unit/blob/master/length.go 25 | 26 | const ( 27 | Meter Length = 1e0 28 | Inch = Meter * 0.0254 29 | Foot = Inch * 12 30 | ) 31 | 32 | // Meters returns the length in m 33 | func (l Length) Meters() float64 { 34 | return float64(l) 35 | } 36 | 37 | func main() { 38 | fmt.Printf("%T\n", dpiValue) 39 | dpiValue := 22.33 40 | fmt.Printf("%v\n", dpiValue) 41 | // dpiValue = "test" // this results in error 42 | 43 | m := 1 * Meter 44 | fmt.Println(m) 45 | 46 | ft := 1 * Foot 47 | fmt.Println(ft) 48 | 49 | fmt.Println(ft.Meters(), "meters") 50 | 51 | fiveMeters := 5.0 * Meter 52 | fmt.Println(fiveMeters) 53 | } 54 | -------------------------------------------------------------------------------- /108-invariant/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Go has the following nillable types: pointers, functions, maps, interfaces, channel and slices 3 | https://github.com/golang/go/issues/33078 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "fmt" 10 | ) 11 | 12 | type NonEmptyList interface { 13 | nonEmptyList() 14 | } 15 | 16 | type Order struct { 17 | menu []int 18 | } 19 | 20 | func (o Order) nonEmptyList(n []int) { 21 | if len(n) <= 0 { 22 | panic("Empty1") 23 | } 24 | if n == nil { 25 | panic("Empty2") 26 | } 27 | fmt.Println() 28 | fmt.Println("n is ", n) 29 | } 30 | 31 | func main() { 32 | 33 | order := Order{ 34 | menu: []int{1, 2, 3}, 35 | } 36 | fmt.Printf("%#v", order) 37 | fmt.Printf("%v", order.menu) 38 | test := order.menu 39 | order.nonEmptyList(test) 40 | 41 | } 42 | -------------------------------------------------------------------------------- /108-invariant2/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Exploring lack of array/slice covariance in Go 3 | https://stackoverflow.com/questions/3839335/any-sensible-solution-to-the-lack-of-array-slice-covariance-in-go 4 | */ 5 | package main 6 | 7 | import "fmt" 8 | 9 | type List interface { 10 | At(i int) interface{} 11 | Len() int 12 | } 13 | 14 | func printItems(header string, items List) { 15 | for i := 0; i < items.Len(); i++ { 16 | fmt.Print(items.At(i), " ") 17 | } 18 | fmt.Println() 19 | } 20 | 21 | type IntList []int 22 | type FloatList []float64 23 | 24 | func (il IntList) At(i int) interface{} { return il[i] } 25 | func (fl FloatList) At(i int) interface{} { return fl[i] } 26 | 27 | func (il IntList) Len() int { return len(il) } 28 | func (fl FloatList) Len() int { return len(fl) } 29 | 30 | func main() { 31 | var iarr = []int{1, 2, 3} 32 | var farr = []float64{1.2, 2.0, 3.0} 33 | printItems("Integer array:", IntList(iarr)) 34 | printItems("Float array:", FloatList(farr)) 35 | } 36 | -------------------------------------------------------------------------------- /109-invariant3/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type Session struct { 6 | ready bool 7 | orders []int 8 | } 9 | 10 | func (s Session) createSession(i []int) interface{} { 11 | var session Session 12 | fmt.Println("i is ", i) 13 | if len(i) > 0 { 14 | fmt.Println("OK") 15 | session = Session{orders: i, ready: true} 16 | return session 17 | } 18 | panic("No proper data") 19 | } 20 | 21 | //--------------- 22 | 23 | func main() { 24 | fmt.Println("--------------------------") 25 | 26 | var t *Session 27 | fmt.Printf("t: %#v\n", t) 28 | 29 | b := (*Session)(nil) // (*main.Session)(nil) 30 | fmt.Printf("b: %#v\n", b) 31 | 32 | // This causes a panic 33 | // if t == nil { 34 | // t.createSession([]int{1, 2, 3}) 35 | // } 36 | 37 | if b == nil { 38 | b = new(Session) 39 | b.orders = []int{3, 3, 4} 40 | b.ready = true 41 | } 42 | fmt.Println("t is ", t) 43 | 44 | } 45 | -------------------------------------------------------------------------------- /110-capturing-rules/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/capturing-rules 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /110-capturing-rules/main.go: -------------------------------------------------------------------------------- 1 | // "Make illegal states unrepresentable." Go-style 2 | package main 3 | 4 | import ( 5 | "fmt" 6 | vs "github.com/capturing-rules/verificationservice" 7 | ) 8 | 9 | func chooseCustomerEmail(c vs.CustomerEmail) vs.CustomerEmail { 10 | switch value := c.(type) { 11 | case vs.Unverified: 12 | fmt.Printf("UNVERIFIED: %v\n", value) 13 | return value 14 | case vs.Verified: 15 | fmt.Printf("VERIFIED: %v\n", value) 16 | return value 17 | 18 | default: 19 | fmt.Printf("%v: %T\n", value, value) 20 | panic("Wrong type") 21 | } 22 | } 23 | 24 | func Check(email string) { 25 | checkverification := vs.CheckVerification(email) 26 | if checkverification != (vs.Verified{}) { 27 | verified := vs.Verified(checkverification) 28 | // Prevent direct assignment via `verified := vs.Verified{Email: "izumi@omame.com"}`` 29 | chooseCustomerEmail(verified) 30 | } else { 31 | fmt.Printf("FAIL CHECK: %v\n", email) 32 | } 33 | } 34 | 35 | // The workflow that sends a password-reset message must take a 36 | // VerifiedEmailAddress parameter as input rather than a normal email address. 37 | type SendPasswordResetEmail func(vs.Verified) 38 | 39 | var sendpasswordresetemail SendPasswordResetEmail = func(email vs.Verified) { 40 | // do logic here 41 | fmt.Printf("%T\n", email) 42 | fmt.Println("sendpasswordresetemail") 43 | } 44 | 45 | //----- 46 | 47 | func main() { 48 | fmt.Println("-----------------------") 49 | 50 | unverified := vs.Unverified{Email: "george@omame.com"} 51 | chooseCustomerEmail(unverified) 52 | 53 | Check("izumi@omame.com") 54 | Check("jiko@omame.com") 55 | 56 | // very hacky 57 | checkverification := vs.CheckVerification("george@omame.com") 58 | verified := vs.Verified(checkverification) 59 | sendpasswordresetemail(verified) 60 | } 61 | 62 | /* 63 | Notes: 64 | 65 | F# code: 66 | 67 | type Record = { Name: string; Age: int } 68 | 69 | type Abc = 70 | | A 71 | | B of double 72 | | C of Record 73 | 74 | The Abc type defined above can be either A, B, or C 75 | (and nothing else). 76 | In the case of A there is no "payload". 77 | With B and C there is a payload that comes with it. 78 | 79 | In Go, we can assume the tags will always come with a payload, 80 | eg there is no distinction between tag and type. 81 | 82 | 83 | Reference 84 | https://stackoverflow.com/questions/28800672/how-to-add-new-methods-to-an-existing-type-in-go 85 | */ 86 | -------------------------------------------------------------------------------- /110-capturing-rules/verificationservice/verificationservice.go: -------------------------------------------------------------------------------- 1 | package verificationservice 2 | 3 | type CustomerEmail interface { 4 | isCustomerEmail() 5 | } 6 | 7 | type Unverified struct { 8 | Email string 9 | } 10 | 11 | type VerifiedEmailAddress struct { 12 | email string 13 | } 14 | 15 | type Verified VerifiedEmailAddress 16 | 17 | func (u Unverified) isCustomerEmail() {} 18 | func (v Verified) isCustomerEmail() {} 19 | 20 | var ( 21 | _ CustomerEmail = (*Unverified)(nil) 22 | _ CustomerEmail = (*Verified)(nil) 23 | ) 24 | 25 | func CheckVerification(i string) Verified { 26 | // do check here 27 | // assume it passes 28 | if i == "jiko@omame.com" { 29 | var VEA = Verified{ 30 | email: i, 31 | } 32 | return VEA 33 | } 34 | return Verified{} 35 | } 36 | -------------------------------------------------------------------------------- /111-sumtypes/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | //--- 8 | 9 | type ContactInfo interface { 10 | isContactInfo() 11 | } 12 | 13 | type EmailContactInfo struct{ name string } 14 | type PostalContactInfo struct{ name string } 15 | 16 | type BothContactMethods struct { 17 | EmailContactInfo 18 | PostalContactInfo 19 | } 20 | 21 | func (e EmailContactInfo) isContactInfo() {} 22 | func (p PostalContactInfo) isContactInfo() {} 23 | func (b BothContactMethods) isContactInfo() {} 24 | 25 | // https://www.toptal.com/go/your-introductory-course-to-testing-with-go 26 | // Make sure we can create variables of this type, w/o incurring any costs 27 | // A great memory-free trick for ensuring that the interface is satisfied at 28 | // run time is to insert the following into our code: 29 | // This checks the assertion but doesn’t allocate anything, which lets us make 30 | // sure that the interface is correctly implemented at compile time, before the 31 | // program actually runs into any functionality using it. 32 | var ( 33 | _ ContactInfo = (*EmailContactInfo)(nil) 34 | _ ContactInfo = (*PostalContactInfo)(nil) 35 | _ ContactInfo = (*BothContactMethods)(nil) 36 | ) 37 | 38 | func chooseContactInfo(c ContactInfo) ContactInfo { 39 | switch value := c.(type) { 40 | case EmailContactInfo: 41 | fmt.Printf("EmailContactInfo: %v", value) 42 | return value 43 | case PostalContactInfo: 44 | fmt.Printf("PostalContactInfo: %v", value) 45 | return value 46 | case BothContactMethods: 47 | fmt.Printf("BothContactMethods: %v", value) 48 | return value 49 | default: 50 | fmt.Printf("default: %v", value) 51 | panic("WRONG TYPE") 52 | } 53 | } 54 | 55 | //----- 56 | 57 | type Contact struct { 58 | Name string 59 | ContactInfo 60 | } 61 | 62 | //----- 63 | 64 | func main() { 65 | fmt.Println("---------------------------") 66 | emailcontactinfo := EmailContactInfo{name: "george@omame.com"} 67 | chooseContactInfo(emailcontactinfo) 68 | 69 | name := "George" 70 | 71 | contact := Contact{ 72 | Name: name, 73 | ContactInfo: emailcontactinfo, 74 | } 75 | fmt.Printf("%+v\n", contact) 76 | } 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 George Baptista 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ddd-in-go 2 | 3 | - Attempt to port "Domain Modeling Made Functional" by Scott Wlaschin from F# to Go 4 | 5 | 6 | --------------------------------------------------------------------------------