
Introduction
The Twelve-Factor App describes configuration beautifully simple as a ‘strict separation of config from code’. Is it really that simply in practice? In my experience, configuration can suffer from the same rot and neglect as any other code.
In this article, I’m going to demonstrate how to make date and time configuration easier to use, easier to read and easier to maintain. The examples are derived from a refactoring exercise taken by our team. They are meant to be simple to adopt in both new and existing applications and thus a great way to leave your code a little bit better than it was yesterday.
What?
So much of the natural world revolves around date or time periods so we often have to represent these in applications. We generally work using different environments such as development, qa, user acceptance and production due to the increase in speed to market as new changes can be made even while existing changes are tested.
Similarly these time periods will often be environment specific. For example, let’s assume that we want to ask a customer for feedback 3 days after they have placed an order for our product. It would be a lot simpler to validate this if we could easily reduce this duration to say 5 minutes after the order.
Java 8 introduced the Date & Time API (JEP 150) to support standard date, time and calendar concepts. Many developers are familiar with LocalDateTime or Instant classes which represent a point in time. Duration and Period are classes which are useful to represent the time spent between 2 points in time. Both are based on the ISO-8601 standard.
Spring Boot allows you to externalise configuration from an application and either inject or programatically access the values. One of the most useful ways of injecting the values is to bind properties to variables. For example the property contact.number can be bound to a variable of type String:
@Value("${contact.number}") String contactNumber
Spring Boot has allowed property binding to the Duration class since version 2.0. This has been enhanced in Spring Boot 2.3 to allow binding to the Period class as well.
Why?
Previously application developers would bind a numeric value and attempt to programatically compute durations of time:
feedback-request.sent-after.in-days=3 @Value("${feedback-request.sent-after.in-days}") int feedbackAfterInDays LocalDateTime feedbackRequestDate = orderDate.plusDays(feedbackAfterInDays)
But what if we wanted the ability to ask for feedback 5 minutes after the order? We could rewrite the above to use minutes as the lowest common denominator:
feedback-request.sent-after.in-minutes=5 @Value("${feedback-request.sent-after.in-minutes}") int feedbackAfterInMinutes LocalDateTime feedbackRequestDate = orderDate.plusMinutes(feedbackAfterInMinutes)
Brilliant! Only now the production code is difficult to read. Not the sort of cognitive load you want to face when investigating a bug:
feedback-request.sent-after.in-minutes=4320 // 3 days in minutes
Spring Boot property binding of a Java 8 Duration allows the above to be refactored to:
feedback-request.sent-after=P3D // "A period of 3 days" @Value("${feedback-request.sent-after}") Duration feedbackAfter LocalDateTime feedbackRequestDate = orderDate.plus(feedbackAfter)
Changing the value to 5 minutes is a simple case of:
feedback-request.sent-after=PT5M // "A period of time of 5 minutes"
Even a simple change like this brings several advantages.
Readability – the fact that the unit of measurement happens to be weeks is part of the value of the property; not the name of the property which is meant to help you understand what the property refers to.
Usability – Logic on dates and times may be something simple such as timeOnMarket.minusWeeks(timeOnMarketMaxAllowedInWeeks). Even something as simple as this can be simplified using native java.time classes to timeOnMarket.minus(timeOnMarketMaxAllowed).
Maintainability – There is move flexibility in how we specify the value. Changing a value from 15 minutes (feedback-request.sent-after.in-minutes
) to 6 hours no longer requires expressing the value as 360 minutes or adding a new property (feedback-request.sent-after.in-hours
) to allow new versions of the service to use the new value but still support rollback.
It took around 15 minutes of my Friday to put together a pull request for such a change. The scope was initially limited to a single class in a single application. I wanted to hear the opinion of both a QA and a developer and was pleased to hear both thought it looks good. A few weeks later we’ve started writing new code using this approach. Old code is being migrated as we touch services to add new features or maintenance. Our team has found it easier to debug when investigating potential issue and faster to develop when making changes.
How?
Spring Boot supports binding durations and periods to:
- The standard ISO-8601 format used by
java.time
- For simple cases, a format where the value and the unit are coupled (e.g.
10s
means 10 seconds)
Spring Boot bind | Value | Parses as | Notes |
---|---|---|---|
@Value(“${PT15M}”) Duration example | PT15M | 15 minutes | |
@Value(“${P3M}”) Duration example | P3M | 3 months | |
@Value(“${P2DT3H4M}”) Duration example | P2DT3H4M | 2 days, 3 hours and 4 minutes | |
@Value(“${P1Y2M3W4D}”) Period example | P1Y2M3W4D | 1 year, 2 months and 25 days | weeks are stored as days |
@Value(“${P1Y2M3D}”) Period example | P1Y2M3D | 1 year, 2 months and 3 days | |
@Value(“${5m}”) Period example | 5m | 5 months | but minutes and months are less clear without reading the source code |
@Value(“${5m}”) Duration example | 5m | 5 minutes | clearer than the number 5 on it’s own |
Reference:
- https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.conversion.durations
- https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.external-config.typesafe-configuration-properties.conversion.periods
Conclusion
The 12 factor app makes it easy to reason about configuration, but there are many specific examples to consider such as time and durations. The Java Time API and Spring Boot work well together to provide a solution to this challenge. Previously we might have used numbers and strings to try to bind time to them and then manually convert the values. Using these special purpose classes makes it easier to reason about time and maintain the code in the future. In my experience, adopting this technique was mostly quick and easy but provided real improvements to the developer experience.