Go: Managing Cross Concerns Using Proxy Pattern

Muhammad Farag
Jan 12 · 3 min read

Cross concerns such as logging, instrumentation and authorization are like bad contractors. One needs them, but they tend to leave a mess. A cross concern violates the single responsibility principle which states that a class or a module should have one and only one reason to change. Violating this principle results in code that is difficult to understand with intertwined rules. The core business of the application gets buried under tons of unrelated code, which extends to tests as well. A test case will either test multiple unrelated concerns or be duplicated for each concern.

In this post, I am exploring how proxy pattern can help ease this friction. The example given here is very trivial and meant to demonstrate the approach while not getting distracted by a complex business problem. In the end, we are going to go over the benefits of using such approach.

We have a simple code, a RemoteClient that satisfies Client interface. It has one method RetrieveUser, which in turn calls some IO library. This code is easy to read and understand.

We want to add some cross concern to this code. We will take logging as an example.

The business logic is buried under tons of log statements. The only reason we have the error check here, is to write the appropriate log. This problem would have been even worse if we had other concerns. Note that RemoteClient now depends on logging. In this case it was by being a member of RemoteClient, but it could have been via a static import, it is still a dependency.

One solution for this problem is using the proxy pattern. We create a new type ClientLogger that satisfies the same interface RemoteClient does. Then each method of the new type ClientLogger delegates to the corresponding method in RemoteClient. That is ClientLogger.RetrieveUser calls RemoteClient.RetrieveUser with the same exact arguments, and return the same results. However, we wrap the call to RemoteClient.RetrieveUser with our cross concern.

Since the code calling our RemoteClient depends on Client interface ,hopefully, we only need to make a minor change to the client instantiation from

to

If our application retrieves its implementation from a factory this change should be transparent to the business logic. Only the factory method will be updated.

If we have even more concerns, our code will not change. But our client instantiation would get just a little bit messy.

What did we gain?

  • Each of ClientLogger and RemoteClient has a single responsibility, and a single reason to change. If the IO library changes, the logging shouldn't change. If logging format changes, our RemoteClient shouldn't care. If authorization rules change, neither logging nor RemoteClient should care
  • We can have three different types of Clients, and we wouldn’t need to duplicate the cross concern code, also known as DRY (Don’t Repeat Yourself)
  • Significantly decrease the cognitive load. One can easily understand and navigate through the code without being hindered by cross concerns or jumping from one concern to another.
  • Each concern can be tested and verified independently.

Why write when you can generate!

GoWrap is a command line tool that would create wrappers based on your interfaces. So, you can use it to generate your cross concern handlers. It already has templates for most common concerns, but you can define your own. Gratitude goes to titpetric for pointing it out.

I think I would still write my wrappers instead of generating them because I don’t think generated wrappers are flexible enough, they might, however, be a good starting point.


Originally published at mfarag.com.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade