Skip to content

Instantly share code, notes, and snippets.

@xpepper
Last active January 27, 2025 22:35
Show Gist options
  • Save xpepper/f48ff8be2ace33ca586284a68b424cb6 to your computer and use it in GitHub Desktop.
Save xpepper/f48ff8be2ace33ca586284a68b424cb6 to your computer and use it in GitHub Desktop.
Domain Modeling Made Functional - What are other practical thought exercises I could do at the end of chapter 6?

Here are some practical exercises we could do:


1. Identify Invariants in Your Own Domain

Think about a real-world system you're familiar with (e.g., a shopping cart, a library management system, a flight booking system). For each one:

  • List out the core entities in the domain.
  • Identify the key business rules (invariants) that must always hold true.
  • Ask yourself: How would you model these rules as types and functions?

For example:

  • Shopping Cart:
    • Invariant: A cart must have at least one item before it can be checked out.
    • Invariant: Item quantities must be positive integers.
  • How would you represent these invariants in F#?

2. Encode a Complex Invariant

Pick a domain with a slightly more complex rule and attempt to model it. For example:

  • A valid password must:
    • Be at least 8 characters long.
    • Contain at least one number.
    • Contain at least one uppercase and one lowercase letter.
    • Contain no spaces.

Write a function in F# that enforces this invariant and returns an Ok result for valid passwords or an Error message otherwise.


3. Model a Workflow with Invariants

Workflows often have invariants tied to the steps in the process. For example:

  • Loan Application Process:
    • An application cannot be submitted unless all required fields are filled out.
    • Once submitted, it cannot be modified.
    • A loan application can only be approved if it has been submitted first.

Create a simple F# model for this workflow, encoding the states (e.g., Draft, Submitted, Approved) as a discriminated union and ensuring transitions respect the invariants.


4. Explore Real-World Invalid States

Think about a real-world system you’ve used that frequently breaks or behaves incorrectly. Identify examples of invalid states you’ve encountered and consider:

  • What invariants were likely violated?
  • How could the system's domain model have been designed to prevent those invalid states?

For example:

  • A payment processing system might fail when the user submits an expired credit card. An invariant for a "ValidPaymentMethod" type might include checking the expiration date upfront.

5. Refactor a Model to Remove Invalid States

Take a poorly-designed domain model (real or imagined) and refactor it to remove potential invalid states. For instance:

  • Problem: A Customer record has a PhoneNumber field that allows any string.
  • Solution: Replace string with a custom PhoneNumber type that enforces the invariant: "A phone number must follow a valid format."

Write out both the before and after versions of the model to see how the invariant improves the design.


6. Combine Multiple Invariants

Model an entity with multiple, overlapping invariants. For example:

  • Conference Registration:
    • The registration fee must be paid before a ticket can be issued.
    • A registrant must select a valid ticket type (e.g., Standard, VIP).
    • The conference capacity cannot be exceeded.

Design a set of types and functions in F# to represent this scenario and enforce all three invariants.


7. Play with Optional Values

Optional values (Option in F#) can sometimes represent missing data. Experiment with when and how to use Option types to enforce domain invariants. For example:

  • A delivery address might be Some for physical products and None for digital downloads. How would you model this distinction so that it's impossible to assign a None address to a physical product?

8. Validate Nested Structures

Create a deeply nested data structure with multiple layers of invariants. For instance:

  • University Enrollment:
    • A student must have a valid ID and email.
    • A course must have a valid code and an assigned professor.
    • Enrollment must link a valid student and a valid course.

Design a validation pipeline for this system, where each layer validates its own invariants.


9. Experiment with Error Handling

When enforcing invariants, errors can occur. Think about:

  • How to give useful error messages when an invariant is violated.
  • Whether to stop processing on the first error or accumulate all errors.
  • Implement a function that tries to create an entity and returns a list of errors if validation fails.

For example, if you're validating a User object:

  • Email is invalid.
  • Password is too short.
  • Name contains invalid characters.

How would you report all three issues at once instead of stopping at the first failure?


10. Write Code with Extreme Invariant Enforcement

Pick a small domain and enforce every possible invariant you can think of at the type level, ensuring that invalid states are completely unrepresentable. Use:

  • Record types for structured data.
  • Union types for constrained choices.
  • Validation functions for more complex rules.

Example domain: Pizza Order

  • A pizza must have at least one topping.
  • Topping choices must come from a predefined list.
  • Order size must be "Small", "Medium", or "Large".

How would you model this in F#?


11. Compare Functional vs. OO Approaches

If you’re more familiar with object-oriented programming (OOP), take a domain and model it in both OOP and functional programming styles:

  • What’s easier in each style?
  • How does enforcing invariants differ between the two?

Focus on how functional programming (with immutable types and pure functions) encourages you to build your model in a way that prevents invalid states.


12. Real-Life Application

Think about a current project you're working on or a problem you'd like to solve. Write down:

  • The key entities and relationships.
  • The invariants for each entity.
  • One area where enforcing invariants could eliminate a common bug.

Try coding a small portion of the domain in F# using the principles in the chapter.


Closing Tip

For each exercise, ask yourself:

  • Did you eliminate invalid states from the domain model?
  • Are the invariants clear and well-encapsulated?
  • Could someone new to the code easily understand what makes a state valid?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment