1

What are best practices for modeling "arithmetic business types" like money or time?

Is it recommended to make classes with explicit plus/multiply operations? Should there be a class for every currency?

Or is it more practical to not model them at all with the type system and keep them as BigDecimal?


Example: The business has a concept called "work day" (defined as 8h)

Work days can be added or subtracted. Work days can also be multiplied by money. E.g. to calculate a price.

My first thought was to have the classes WorkDay and Money. But considering the arithmetical operations, there would be the need for a lot of "boilerplate code".

  • Any book recommendation about combining DDD with arithmetical types would be greatly appreciated :-) – Fabian Apr 14 at 12:29
  • 1
    can you give a specific example. most languages have inbuilt types for money (at least decimal) and time. But I assume you mean specific business measures – Ewan Apr 14 at 12:30
  • I've added an example to my question. – Fabian Apr 14 at 12:56
  • 1
    yeah it seems like a marginal case if any for making classes for those things, although sometimes it can be justified if you have more complex stuff to do with them – Ewan Apr 14 at 13:35
  • 1
    F# supports "Units of Measure." See fsharpforfunandprofit.com/posts/units-of-measure – Robert Harvey Apr 15 at 18:11
2

I hardly ever have cause to implement arithmetic operations on my business classes.

Business rules tend to be fairly simple and not new forms of mathematics. So in your case if 1 + 1 "business days" = 2 "business days" then there is no need to implement the + operator on the BusinessDay class, or indeed probably to have a business day class at all!

Plus business rules tend to change and number of days worked might need to be reevaluated when they do. This is hard if you only have the result of your calculations done at the time.

Probably you want to record facts like 'Hours Worked' and then do a calculation on those hours to work out how many business days that is. 1 + 1 business days might actually = 1.5

Similarly with money.

Occasionally you can do something very elegant with new types and operator overrides for them, but unless your rules are set in stone and the elegance very intuitive to use its more artistic than practical.

2

To echo what @Ewan is saying, to me it is more about properly capturing the various types and units involved in what your domain measures and the way it computes than about providing methods for the actual arithmetic needed.

You may need to anchor your hours worked with specific start and stop times, to capture a pay rate schedule (e.g. holidays & weekends) so you can determine what rate for which hours, to understand how hours worked falls into pay period boundaries for overtime calculations, etc..


Modern languages have extensive notions of date/time, duration, and units (hours, days, ...) that can be added and subtracted.  Subtracting two times yields a duration, which can be added to a (different) time.  Potentially very error prone stuff, probably no need to reinvent that.

For money, determine the system of currencies and accuracies you need, especially if internationalization is involved; rounding modes for various locales and practices if you're doing division or percentages (or fractional multiplications).

3

Or is it more practical to not model them at all with the type system and keep them as BigDecimal?

That depends: how to you want to mitigate the errors that arise when somebody tries to add a Money to a WorkDay?

For the most part, your compute doesn't care at all about your domain model. It is just looking at registers, bytes in memory, and so on -- completely domain agnostic.

The design of your code servers your programmers, by helping them to avoid mistakes. Sometimes that help will be purely semantic -- tags to prevent the programmer from accidentally applying the domain concepts incorrectly. If you have automatic type checking, then there is a class of errors that you can eliminate, and you can take advantage of the type system to manage certain domain invariants for you as well.

We want fewer mistakes, because we want to be able to more accurately predict the cost of introducing new features into the code.

If everything is a BigDecimal, then generic checks have a very difficult time distinguishing correct from incorrect code, and you fall back toward other mitigation strategies (lots of testing; optimism).

Should there be a class for every currency?

It depends? Currencies are really different units in the same dimension, is the same way that meters and feet are different units of length, or degree Celsius and degree Fahrenheit are different units of temperature. Bugs that confuse the two can be very bad; on the other hand, there's not really much variation in behavior, so using a common Money property with some currency code meta data might be more practical than trying to manage distinct types for each.

  • "If you have automatic type checking, then there is a class of errors that you can eliminate, and you can take advantage of the type system to manage certain domain invariants for you as well." This is getting to my point. I would like to have type safety (maybe only as type aliases). But how can I have type safety without reimplementing basic arithmetical operations? (or is this not possible and it should in general not be done, because it would be impractical?) – Fabian Apr 15 at 7:38
  • You can also use existing APIs such as Java Money API (JSR 354). For existing units-of-measure APIs for Java programmers see stackoverflow.com/questions/6474658/… – DodgyCodeException Apr 29 at 13:54
0

The concept is called "Units of Measure" (thx at Robert Harvey for pointing it out)

Great Blog Post: https://mikhail.io/2015/08/units-of-measurement-in-domain-design/

F# has it as a language construct:

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.