Examples from Domain Modeling Made Functional Tackle Software Complexity with Domain-Driven Design and F# book translated to Kotlin
I was looking for production like examples of code based on Functional Programming paradigm. I found the aforementioned book which focuses on using Functional Programming with Domain Driven Design. The book contains extensive code samples So I found even more than I was looking for ;-).
I try to apply FP but in Kotlin. So I thought that it could be interesting to convert the code from book to Kotlin.
It is not 1 to 1 copy. I have changed the original in several areas:
- implementation is based on Arrow - functional library for Kotlin
- application assembly and REST endpoint is based on Spring Boot
- data validation is based on arrow types
- base type for simple types to support comparison and string representation -> replaced with value classes where possible
- dropped String50 type
- dependencies - only immediate dependencies are provided instead of dependencies of dependencies
- orderId and orderLineId are alphanumeric with capital letters only
The code in /src/original directory was copied from sources attached to the book.
To start the application execute following command:
./gradlew bootRun
Execute the following command in shell:
curl --location --request POST 'http://localhost:8080/orders' \
--header 'Content-Type: application/json' \
--data-raw '{
"orderId": "ORD1",
"customerInfo": {
"firstName": "Jan",
"lastName": "Kowalski",
"emailAddress": "jan@kowalski.com"
},
"shippingAddress": {
"addressLine1": "Some Street 1",
"city": "Los Angeles",
"zipCode": "12345"
},
"billingAddress": {
"addressLine1": "Other Street 4",
"addressLine2": "Appatment 42",
"city": "Beverly Hills",
"zipCode": "90210"
},
"lines": [
{
"orderLineId": "LN1"
"productCode": "W1234",
"quantity": 10
},
{
"orderLineId": "LN2",
"productCode": "G123",
"quantity": 0.75
}
]
}
'
Execution of following command
curl --location --request POST 'http://localhost:8080/orders' \
--header 'Content-Type: application/json' \
--data-raw '{
"orderId": "1345",
"customerInfo": {
"firstName": "Jan",
"lastName": "Kowalski",
"emailAddress": "jan@kowalski.com"
},
"shippingAddress": {
"addressLine1": "Some Street 1",
"city": "Los Angeles",
"zipCode": "12345"
},
"billingAddress": {
"addressLine1": "",
"addressLine2": "Appatment 42",
"city": "Beverly Hills",
"zipCode": "90210a"
},
"lines": [
{
"orderLineId": "LN1",
"productCode": "W1234",
"quantity": 101111
},
{
"orderLineId": "LN2",
"productCode": "G123",
"quantity": 0.001
}
]
}
'
Should return validation errors:
{
"code": "ValidationError",
"message": "Validation errors",
"validationErrors": [
{
"path": "billingAddress/addressLine1",
"message": "The length of <> should be between <1> and <50>"
},
{
"path": "billingAddress/zipCode",
"message": "'90210a' must match the pattern '\\d{5}'"
},
{
"path": "lines[0]/quantity",
"message": "The <101111> should be between <1> and <1000>"
},
{
"path": "lines[1]/quantity",
"message": "The <0.001> should be between <0.05> and <100.00>"
}
]
}
The validation incorporates address check which is an asynchronous operation. Because of that it enforces that address and order validation become also synchronous. Async/suspend does not blend well with Validated. Need to find a smart way to solve the problem.