Design Patterns: Who gives a 💩?

Should you care about design patterns? There are books devoted to them; heck, even I post videos about specific design patterns. But do they matter? If you’re new to design, it can be overwhelming and cause a lot of unneeded complexity. I will cover how I think of design patterns or how I don’t think of design patterns.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Bell Curve

In my own personal experience, and witnessing the same occur to other developers in their careers, is a mid-career explosion of complexity. Design Patterns play a big role in this explosion of complexity.

Early on in a developer’s career, they are often righting pretty simple code. While that code may be highly coupled and flawed, they generally write “simple code.”

Beginner Simplicity
The vertical-axis is the level of complexity; the horizontal axis is the level of experience.

Now we can argue what “simple code” means, but speaking for myself, it was straightforward without any magic. You aren’t writing “smart” or “clever” code. What you see is what you get. There was little indirection.

As you gain more experience and read or watch various tutorials/courses/books, you start seeing every problem as a means to solve by patterns.

There is a similar case to be made for doing the same thing with the latest technology, library, framework, or platform. You learn something new and immediately want to apply it. Unfortunately, this often leads to using it aimlessly. Meaning you have a hammer, so everything starts looking like a nail.

Design Pattern Complexity

Now you might be thinking, “really, people are just applying patterns for no good reason?”. Yes, this is more common than you think. It can also be because you’re applying patterns because you think you have the problem it solves. More on that later.

Hopefully, you feel enough pain in this phase of your career where you can realize you aren’t solving problems but rather creating unneeded complexity. On the bright side, you’ll better understand various patterns and the problems they solve, as you’ve used them for the right and wrong reasons.

On the other side of the complexity, the nightmare phase is back to the simplicity that resembles the naive code you’ve written at the beginning of your career.

Simplicity with deeper insight

That’s not to say you’re writing beginner code, but it’s focused, trivial, with no magic, and less useless indirection. It’s direct and to the point. As someone commented on the YouTube video:

It took me four years to paint like Raphael, but a lifetime to paint like a child.

Pablo Picasso

Communication

A key to patterns is communication. Named patterns are a way to communicate between developers about solutions and implementations for various problems. For example, one developer is explaining a problem they are having with another developer. The other developer says that the problem could be solved by [Insert Named Pattern]. If both developers understand the named pattern, they don’t have to get lost in the deep implementation details of that pattern. They already understand it. It’s a communication tool.

According to many comments on various videos I’ve done on YouTube, it’s common for people to apply a pattern without even realizing it’s a named pattern. I have done this countless times over my career. You’re faced with a problem and come up with a solution that turns out to be a named pattern! Once you realize this, great! You understand both the problem the pattern truly solves and how to implement it.

Knowing the names of patterns and the problems they solve is great for communication.

Avoiding the Problem

Earlier I mentioned that you could apply patterns for problems you think you have. However, it’s often helpful to examine why you have the problem. Meaning one solution is to avoid the problem in the first place.

To illustrate this, I’m going to use the example of the Repository Pattern. Now you could take this example more abstractly and apply it to other patterns that add indirection.

There are different definitions of the repository pattern, but for this example, I’ll say it’s used to encapsulate data access logic.

Mixing data access logic with other concerns sounds like a terrible idea. However, there is an underlying issue that’s not talked about. Coupling.

With the repository, you’re coupling to it rather than likely using a native database provider directly. The purpose of abstractions is to simplify the interface for your purpose. The repository pattern can do this for us. Great. However, you still have the same degree of coupling from your application code using the repository.

If you have hundreds (or thousands) of usages of the repository in your application code, you have a high degree of coupling to your repository. If you make any breaking changes to your repository, you’re faced with changing all calling code that breaks.

Another solution is to limit coupling.

In many situations, it’s not that you need all the data or implementation of the repository. Often you only need a subset. The repository might not be ideal in every situation. I talked about this in my post Should you use the Repository Pattern? With CQRS, Yes and No!

Using your repository abstraction might be helpful in one situation and not ideal in another. You may decide a subset of features is grouped together, use the same related data, and use a repository. Another subgroup of features might choose to access data differently because of its use case.

The problem isn’t that you need to encapsulate data access; the problem is you have a high degree of coupling of calling code that needs data access. One solution is to encapsulate data access with a repository. Another is to limit coupling, so the need for encapsulating data access is less of a concern or decided per situation.

Design Patterns

Should you care about design patterns? Yes, absolutely. Understanding the names of design patterns and the problems they solve is helpful. It’s a great way to communicate with other developers when discussing problems and solutions. However, don’t apply a pattern unless you truly have the problem it solves. Do you have the problem it solves? Or should you look at ways to eliminate the problem.

Join!

Developer-level members of my YouTube channel or Patreon get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Stop using trivial Guard Clauses! Try this instead

Guard clauses, on the surface, sound like a great idea. They can reduce conditional complexity by exiting a method or function early. However, I find guard clauses used in the real world to be of little value. Often polluting application-level code for trivial preconditions. I will refactor some code to push those preconditions forcing the edge of your application so your domain focuses on real business concerns.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Null Checks

The most common case of guard clauses is doing null checks. To illustrate this, I looked at the eShopOnWeb sample application and will use it as an example.

In the example above, two guard clauses do null checks on the anonymousId and userName. While this is incredibly common, I’d rather not have to deal with these preconditions mainly because this method is in the BasketService and a part of the core application code.

Often these types of preconditions are very inconsistent. Since the userName is likely passed around through various layers, does each method that accepts the username have this guard clause? Likely not.

As an example, the above code creates a new instance of the Basket passing the userName to the constrictor. Here’s the constructor.

Sure the TransferBasketAsync method was doing the null check, but does this Basket class get created anywhere else? Is it doing a null check? As you can imagine, if you did this everywhere, you’d have a ton of repetitive trivial code for these null check preconditions.

Forcing Valid Values

I don’t want to litter my core application code with trivial guard clauses, such as null checks, as my example. Instead, I want my core code always to accept valid values so that it doesn’t need to concern itself with doing these guard clauses.

In doing so, you’re pushing the responsibility to the outer edge of your application to produce valid values. If you think about a web application, there is some translation from an HTTP request into your application code. You want to force that translation at the edge, which is your web endpoint, into valid domain values.

One way to accomplish this, as in my example with the userName is to define a type (a record struct) that, during creation, forces the value not to be null.

Then we can use this type wherever we accept the userName as a parameter. Instead of accepting a string for the anonymousId and username in the TransferBasketAsync method, we can move this to the Username type.

To call this TransferBasketAsync in the BasketService, you must construct a Username type. In this example, this is done on a Razor page.

In the above example, the userName comes from the HTTP request, which will be a string. We then construct a Username type passing in that string value. We’re pushing the validation to be at the edge of the application.

Guard Clauses

Our core application defines the Username type, but its usage/creation is at the edge. Nothing can get into the core application without being in a valid state.

Tests

Because of guard clauses, such as null checks, there tend to be tests associated with them. This was the case with this eShopOnWeb sample, where there were tests to confirm those null checks were done. But since we’ve moved to a type that forces it to be valid, these tests still passed because it was throwing. However, these tests are useless now, and we can remove them. We can now remove any tests that were related to doing a null check against the string username.

Instead, we have a few tests to confirm the behavior of our new Username type.

Primitive Obsession

As some others commented on the YouTube video, you might be thinking that I’ve introduced a value object to combat primitive obsession. While true, that’s not the seeing the root cause. The root cause isn’t primitive obsession. The issue is allowing your domain to accept invalid arguments that you need to guard against. This can apply to primitives that are null, as my example illustrates, but it can also be explicit invariants for generic examples, Money, or Date/times with Timezones. However, this can include very domain-specific values.

Guard Clauses

I’m not suggesting guard clauses are not helpful. They are. Exiting early within a method when preconditions aren’t met simplifies logic. However, littering trivial guard clauses all over your codebase is not helpful. Force the outer edge of your application to construct valid values that are passed into your core domain code.

Join!

Developer-level members of my YouTube channel or Patreon get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

You also might like

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Just store UTC? Not so fast! Handling Time zones is complicated.

Should you store dates & times in your database as UTC? It’s pretty standard advice if you’re working in a system that needs to record dates and times from many different time zones. But this advice doesn’t hold true when dealing with dates and times in the future; here are some things you need to consider.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Just store UTC? Not really.

You’ll hear/read pretty standard advice to store all dates/times as UTC when storing dates/times in a database. As users enter data into your system, you might convert their specific local date time to UTC and then save that in your database.

For example, say you have a web application where the user must enter a date/time value. They would be specifying it in their local time. This date/time in ISO format would be sent to your Server/API as 2022-08-02T18:00:00-400. This is their local date time with their current time zone offset. Your app would then convert this to UTC, which would be 2022-08-02T22:00:00Z

Then when we need to return data to the client, we fetch it out of the database, which is in UTC, and send that UTC date/time to the client, where it can then convert it to its local date/time.

Sounds straightforward, right?

There are two reasons why I think this is the standard advice given on storing date/times. The first is because you’re standardizing your date/times in your database, which means you can query your data and compare it against the UTC. This allows you to sort, filter, etc., all on a standardized date/time. You won’t need any conversion at runtime to do any sorting and filtering.

The second reason I think this is standard advice is that people are only thinking about date/times that are in the past. And that’s where the most significant problem lies, date/times in the future.

Time zones & Daylight Saving Time

A date/time in the future, converted to UTC as of right now, might change. How? Because Time Zone rules can change. Daylight Saving Time rules can also change.

When you do a conversion from a local date/time as of right now, at this very moment, you’re applying the current timezone and daylight saving time rules. But that’s not to say these rules will always be like this. They change more than you think!

This means that when you convert a local date time that’s in the future to a UTC date/time and then save that to your database, you’re basing on the rules at the time of persistence, not what it would be in the future when that date/time is realized.

So what should you be saving in your database? Let’s walk through an example.

Here is an object with a future date/time with a time zone offset and a location in Toronto, Canada.

As mentioned, if the timezone rules or daylight saving time rules change, this could be incorrect as the time zone offset could be wrong. How would we correct or convert this date time to the correct value? How would we know what the correct value should be? You wouldn’t be able to, as you have no information about what time zone was used. All you have is the time zone offset.

If we were storing UTC instead, we still have the exact same problem. We cannot convert it to the correct time if there are any rule changes.

Time Zone Database

One solution is using the Time Zone Database. If you’re using a library for handling dates and times (eg, JodaTime, NodaTime), it is likely already using the Time Zone Database (tzdb) under the hood. The Time Zone Database contains all time zone boundaries, UTC offsets, and daylight-saving rules.

It also contains a time zone identifier (IANA) that you can use to reference any time you specify a local date. In your database, you can then persist this time zone identifier with your local date/time.

You’ll notice the dateTime property doesn’t have the time zone offset anymore. It’s just the literal date/time the user specified. This is because now we have the IANA time zone name (America/Toronto) that relates to that literal date/time.

As mentioned earlier, the reason to standardize to UTC is so you can query your database effectively for sorting and filtering. We can still do this at the time of persistence in addition to the literal local date/time and time zone name.

So as mentioned, time zone and daylight-saving time rules can change. So what happens if they do? Well, what we can also record is the version of the Time Zone Database. This way, we know what version and rules were used when we converted to UTC.

When rules change, the time zone database will get updated. We can query our database looking for future date/times where the tzdb is an older version and then redo the conversion of the dateTimeLocal using the IANA name to convert back to the correct UTC and update that value in our database.

Just store UTC?

While the standard advice of “just store UTC” can work, it will only work if you’re storing date/time that is in the past. If you need to store date/time in the future, then you want to record the time zone name and the version of the tzdb. And for querying purposes, you’ll want to convert to UTC. And if there ever is a change to any rules, you can update the UTC date/times.

Join!

Developer-level members of my YouTube channel or Patreon get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

You also might like

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design