I ran into a problem this week, and a pretty common one at that while coding. I'd like to say it was an issue of naming but that isn't exactly correct. Naming is hard. Everyone has heard this but even when we come up with great names for our functions sometimes our code still lacks a certain level of clarity.
My example this week comes from a code base using the nest framework for node. I have a controller that I want to implement what nest calls a guard. A guard is pretty much what you would expect it to be, it is a function that ensures a set of criteria is met by a request before trying to execute the controller function. The classic use case for a guard would be authentication. A request comes in, the guard makes sure the request is authenticated before allowing it to continue on to the controller.
What is unique about this particular code base though is that our authentication is being handled by the database we are using, Fauna. Fauna allows you to validate a username or email and password and issues you a token to make subsequent requests with. If the token is not valid, Fauna will throw an unauthorized error. This is a nice feature of the database and one that we were embracing. However, when a request comes in why even try to execute the query if the token is not valid? In most of our other code bases we would have an Authentication Guard that stops an unauthorized request ahead of time. This is also very clear when looking at a controller. You can see the guard as a decorator right before the controller method it is guarding. From a clarity perspective this makes sense, but when we look closer at the performance not so much.
In order to verify our token is valid, we have to make a query to the Fauna database, if Fauna rejects the validation our guard will throw an error, otherwise we continue to our controller where we go ahead and make our query for data. In this case we hit the database twice, once for validation and another time for data, but our data query already has validation baked into it. In this case we could save ourselves a query by skipping the guard altogether. That being said, to the reader of our code, it would appear that we have an unauthenticated route. Sure a token is being passed but it is atypical and somewhat unexpected for our database to be able to handle authentication. This fact is quickly learned, but the guard makes this very clear when reading the code to anyone. We find ourselves in a strange place where readability is at odds with what is most efficient.
What else could we do here? Well one option is to make it so our authentication guard is only checking the presence of our token in the request. If it is not there, then we throw an error without ever making any queries to Fauna, which is quite efficient. If it is there then we allow our query in the controller to do the validation. This brings us back to a single request to Fauna, but still shows our intention in the code for this to be a protected route. That being said, calling this the AuthenticationGuard feels a bit off now. Technically this is a protected route, but the guard isn't really doing the authentication. I like the architecture, but the naming is a bit unclear now.
If we go ahead and rename AuthenticationGuard to SessionTokenGuard, we now have a more descriptive name for what this function is doing. We can now take a look at our controller and clearly see this method has a guard on it that is checking something about a session token, which we can dig into more closely if we need more understanding. We then maintain our efficiency by letting Fauna do the authentication at query time as intended but don't waste time trying to make a query when a token isn't present at all.
This may not be where we land at launch time, but as of now I'm happy with this solution. Balancing clarity and efficiency is one of the most difficult tasks for an engineer. Often we never arrive at the perfect solution if there even is such a thing, but we have to strive to give a bit more thought to these types of issues. They make a huge difference as we onboard new developers or even when we have to go back and make modifications to our own work.
My example this week comes from a code base using the nest framework for node. I have a controller that I want to implement what nest calls a guard. A guard is pretty much what you would expect it to be, it is a function that ensures a set of criteria is met by a request before trying to execute the controller function. The classic use case for a guard would be authentication. A request comes in, the guard makes sure the request is authenticated before allowing it to continue on to the controller.
What is unique about this particular code base though is that our authentication is being handled by the database we are using, Fauna. Fauna allows you to validate a username or email and password and issues you a token to make subsequent requests with. If the token is not valid, Fauna will throw an unauthorized error. This is a nice feature of the database and one that we were embracing. However, when a request comes in why even try to execute the query if the token is not valid? In most of our other code bases we would have an Authentication Guard that stops an unauthorized request ahead of time. This is also very clear when looking at a controller. You can see the guard as a decorator right before the controller method it is guarding. From a clarity perspective this makes sense, but when we look closer at the performance not so much.
In order to verify our token is valid, we have to make a query to the Fauna database, if Fauna rejects the validation our guard will throw an error, otherwise we continue to our controller where we go ahead and make our query for data. In this case we hit the database twice, once for validation and another time for data, but our data query already has validation baked into it. In this case we could save ourselves a query by skipping the guard altogether. That being said, to the reader of our code, it would appear that we have an unauthenticated route. Sure a token is being passed but it is atypical and somewhat unexpected for our database to be able to handle authentication. This fact is quickly learned, but the guard makes this very clear when reading the code to anyone. We find ourselves in a strange place where readability is at odds with what is most efficient.
What else could we do here? Well one option is to make it so our authentication guard is only checking the presence of our token in the request. If it is not there, then we throw an error without ever making any queries to Fauna, which is quite efficient. If it is there then we allow our query in the controller to do the validation. This brings us back to a single request to Fauna, but still shows our intention in the code for this to be a protected route. That being said, calling this the AuthenticationGuard feels a bit off now. Technically this is a protected route, but the guard isn't really doing the authentication. I like the architecture, but the naming is a bit unclear now.
If we go ahead and rename AuthenticationGuard to SessionTokenGuard, we now have a more descriptive name for what this function is doing. We can now take a look at our controller and clearly see this method has a guard on it that is checking something about a session token, which we can dig into more closely if we need more understanding. We then maintain our efficiency by letting Fauna do the authentication at query time as intended but don't waste time trying to make a query when a token isn't present at all.
This may not be where we land at launch time, but as of now I'm happy with this solution. Balancing clarity and efficiency is one of the most difficult tasks for an engineer. Often we never arrive at the perfect solution if there even is such a thing, but we have to strive to give a bit more thought to these types of issues. They make a huge difference as we onboard new developers or even when we have to go back and make modifications to our own work.
Michael Rispoli
Software Engineer & Creative Technologist
Software Engineer & Creative Technologist