µObjects: Where our code meets their code

There's a couple types of places where the code we write interacts with the code someone else wrote. This could be another team, a 3rd party library, or the operating system. When we interact with these components, we're tying our code to their code in some fashion.
The more these libraries are referenced in our code; the tighter we're coupling our code to their code. The issue here is that ... Their code doesn't care about ours.

There's a concept I recently read about; but was innately utilizing known as Asymetric Marriage in chapter 32 of Robert Martin's Clean Architecture.
The basic of this is when you use a libraries code throughout your project you are heavily coupled, or married, to that library. It'll be hard to extract if you ever had to.
The library - It doesn't know or care about your code. It can change on a whim and ... you can't do anything about it.
This marriage is one sided; it's Asymetric.

I see a few types of places where our code is interacting with their code. In general; I refer to these as BookEnds.

We've covered interacting with the Operating System.

A very similar usecase are 3rd party libraries. We'll include other team's artifacts; even if they're in the same company. It's a separately maintained code base; it's 3rd party as far as our code is concerned.

We don't want our code to be tightly coupled to their code. My goal when I'm working with 3rd party code to have it referenced in as few places as possible.
I include the Operating System in the group of 3rd party; but it gets a special distinction, both in my treatment of booEnds, and as a concept - The OSAL (Operating System Abstraction Layer).

Given that NewtonSoft Json.NET is the most downloaded NuGet package; I'll go with the assumption that a lot of code is in an Asymetric Marriage with the library.

There's a lot of code in your project that does something like this.

JObject parsed = JObject.Parse(someJson);

You've done it wrong.

For two reasons.

The use of a static method is a violation of µObject Programming, and bad OOP in general. It prevents testing. I don't want to test the library. I can see (and have contributed to) Json.NET's unit test suite. It can prove to me that it works. I don't need to verify it.
Verifying it is a waste of my time writing the tests. It's a waste of the developers time who come after you trying to understand the json, or modify it. It's a waste of time when you're running the tests. It has a cost; readability and performance; to your own test suite.

When I say 'verifying' I don't mean that you've written a test ensuring JObject.Parse doesn't break; but it's a side effect of having the code under test call JObject.Parse. If your test breaks; did it break because Json.NET changed; or because your code did? Your test has become fragile with multiple reasons to break.

I'll eventually get around to writing on the horrors of static methods, but for now I'll leave it at "Never use static methods".

The second reason you're doing it wrong is that it's... the point of this post; Asymetric Marriage. You have Json.NET all through your code; you're dependent on it doing it's thing; continuing to do it; and that you always need it used in the exact way you're currently using it.

What if things change? I'll switch gears from Json.NET and make up an example of another teams Data Access Layer (DAL). They built you a fantastic DAL and if a record's not found; they return null. This DAL is used all through your code. You've DILLIGENTLY handled the null and turned it into a NullObject to ensure your µObject code never sees a null.
Well done!
The DAL fixed a few bugs and added a GREAT new feature. Gotta have it!
You have ... how many places that use the DAL.FindRecord? Each of these need to be updated to use the more performant DAL.FindRecordFast.
... Whelp... time to update a bunch of code.

Run the unit tests... Failure. ShouldReturnNullObjectGivenNoRecord throws an exception.

A change that went in with the change from version 1 to version 2 of the DAL is that a NoRecordException is thrown instead of returning null. Now you have to update everywhere to wrap the call in a try/catch. This is a change for the same reason to A LOT of places.

This is the epitome of "MAKE IT AN OBJECT". We don't need to go into the details; as I'd have to spec out WAY too much of an imaginary DAL.

An implementation I've done around Json.NET to avoid the Asymetric Marriage is something similar to this.

public interface IJsonObject{
    T Value<T>(string key)
}
public interface IJsonParser{
    IJsonObject JsonObject();
}

public class NewtonSoftJsonObject : IJsonObject{
    public NewtonSoftJsonObject(JObject origin) => _origin = origin;
    public T Value<T>(string key) => _origin.Value<T>(key);
}

public class NewtonSoftJsonParser : IJsonParser{
   public NewtonSoftJsonParser(string json) => _json = json;
   public IJsonObject JsonObject() => JObject.Parse(json);
}

The two class files we have here are the ONLY places in the code that will ever reference NewtonSoft.
Just these two.
If we need to change (like in the case of our DAL) a behavior around null we'd be able to do it here.

I haven't done this; but let's imagine we want to always return a NullJsonObject if the key doesn't exist; which would normally return a null.

Normal usage of NewtonSoft is going to have the following code scattered ALL OVER

JObject parsed = JObject.Parse(someJson);
DataBag dataBag = new DataBag();
JObject field = parsed.Value<JObject>("field");
if(field == null){
    filed = new Default();
}

I realized as I'm typing that thought experiment out that a "NullObject" pattern doesn't fit that style. The thinking that leads to having NullObject's doesn't lend itself to writing this kinda procedural code... procedural hurts me.

If we want to change to a NullJsonObject in our isolated instance it'll change to

public class NullJsonObject : IJsonObject{
    public static readonly IJsonObject Instance = new NullJsonObject();
    private NullJsonObject(){}
    public T Value<T>(string key) => return null;//Yeah; it doesn't work
}
public class NewtonSoftJsonObject : IJsonObject{
    public NewtonSoftJsonObject(JObject origin) => _origin = origin;
    public T Value<T>(string key) => _origin.Value<T>(key) ?? NullJsonObject.Instance;
}

This is a horrible example. A JsonObject can't have 'Null' behavior. BUT - who cares about that. The point is that we have a SINGLE place that must change because a 3rd party library changed. Or; with something like NewtonSoft; we've changed our mind on how we want to behave after using the library.
This isolates that change to a single location. No hunting; no hoping; no slew of tests across unknown number of classes to ensure that all the ones you found behave in the new fashion.

One class.
One place to change.
One place to test.

µObjects are the Single Responsibility Principle taken to the extreme.

Where our code meets ...

Using µObjects will have design patterns and best practices are forced into existence. It's the only way to be able to write code like this. Patterns and practices must be in the code. You can't procedural your way out of the problem. You need to find how the µObjects can work together doing their one simple thing to emerge complex behavior.

We've now seen how we can avoid tightly coupling our code to 3rd party code. Either in a library or OS. Your code maintainability will improve doing this.

Show Comments