function that encrypts the SSN and masks the SSN when reporting errors. Do you dare look at the commit history? It could well have been your past self (we've all been there)!
Humans make mistakes, so let's not expect them to be perfect. The core issue here is that the
type wrapper has failed to communicate the limits of how we want it to be used. It's merely a convention to use
instead of calling the generic
with the raw String. In this article, you'll learn a technique that gets the elm compiler to help guide us towards using data as intended: Exit Gatekeepers.
🔑 Exit Gatekeepers
So how do we make sure we don't log, Tweet, or otherwise misuse the user's SSN? We control the exits.
There are two ways for the raw data to exit. If raw data exits, then we don't have control over it. So we want to close off these two exit routes.
🔓 Unsecure Exit 1 - Public Constructor
If you expose the constructor, then we can pattern match to get the raw SSN. This means that enforcing the rules for how we want to use SSNs leaks out all over our code instead of being in one central place that we can easily maintain.
-- the (..) exposes the constructor
🔓 Unsecure Exit 2 - Public Accessor
Similarly, you can unwrap the raw SSN directly from outside the module if we expose an accessor (also known as getters) which returns the /raw data/. In this case, our primitive representation of the SSN is a String, so we could have an unsecure exit by exposing a
moduleSSNexposing (SSN, toString)
toString (SSNrawSsn) =rawSsn
The public accessor function has the same effect as our publicly exposed constructor did, allowing us to accidentally pass the raw data to our
Think of a Gatekeeper like the Model in Model-View-Controller frameworks. The Model acts as a gatekeeper that ensures the integrity of all persistence in our app. Similarly, our Exit Gatekeeper ensures the integrity of a Domain concept (SSNs in this case) throughout our app.
How to control the exits
To add an Exit Gatekeeper, all we need to do is define every function needed to use SSNs internally within the
module. And of course, each of those functions is responsible for using it appropriately. (And on the other side of that coin, that means that the calling code is free of that responsibility!).
Let's make a function to securely send an SSN. We need to guarantee that:
The SSN is encrypted using the proper key
It is sent to the correct endpoint
It is sent with https
It is masked before being being sent to our error reporting
We don't want to check for all those things everywhere we call this code every time. We want to be able to make sure the code in this module is good whenever it changes, and then completely trust it from the calling code.
Now we can be confident that the calling code will never mistakenly send SSNs to the wrong endpoint, or forget to encrypt them!
Displaying the SSN
What if you only want to display the last 4 digits of the SSN? How do you make sure that you, your team members, and your future self all remember to do that?
You could vigilantly put that in a code review checklist, or come up with all sorts of creative heuristics to avoid that mistake. I like to reach for the Exit Gatekeeper pattern as my first choice. Then you need to check very carefully any time you are modifying the SSN module itself, and you can trust the module and treat it as a blackbox when you're not modifying it.
It's very likely that you'll miss something if you have to think about where SSNs are used throughout your codebase. But it's quite manageable to keep the entire SSN module in your head and feel confident that you're not forgetting anything important.
Here's a simple implementation of our last 4 digits view:
Get tips to improve your elm code every week
Go beyond learning what great elm code
how to write it
Tips you won't find anywhere else to level up your elm skills
We'll never share your email. Unsubscribe any time.
You can start applying the Exit Gatekeeper pattern to your elm code right away!
Here are some steps you can apply:
Notice some data in your codebase that you have to be careful to use safely or correctly
Wrap it in a Custom Type (if you haven't already)
Expose the constructor at first to make the change small and manageable
Get everything compiling and committed!
One by one, copy each function that is consuming your new Custom Type and call it from the new module
Once that's done, you can now hide the constructor, and you now have a proper Exit Gatekeeper for your type!