Hello and Welcome to another edition of TWITTER RESPONDS! From a post announcement on twitter
https://twitter.com/TheQuinnGil/status/1166730201826549761
Our tools can kill maintainability. My thoughts on how Frameworks can be a negative to our products.
Tool Impact On Developer Discipline - Frameworks
GHOST_URL/2019/04/02/tool-impact-on-developer-discipline-frameworks/
there was a question posed on twitter about how to get data from the object.
https://twitter.com/LrsEckrt/status/1166759709359202316
Do you have any examples on how you implement web requests in an OO world now? I follow your content for a while now and try to write objects like you advocate, but the moment i need to make an outgoing webrequest, or have to return an object back to the controller, i seem to need to expose the internal data again somehow. be it by providing at least some getters or a toMap method so that jackson/gson/spring can do something.
I do not have an example... but now I do! I love getting twitter questions.
This is a very frequent question I get about the practices I use. "If we never return our data, how do we return our data?"
A lot like the post about frameworks being destructive to our code - tools like jackson/gson/spring/Json.Net are destructive. These kinda of tools are exceptionally destructive to our object oriented code because they do many things. They do things FOR us; which is great when it is done in an object oriented fashion. Few do, so few work well.
All of the JSON convertors tend to require some fashion of properties and reflection to accomplish the task. This bleeds into our code, forcing properties. Forcing our code to know about this tool.
It's destructive.
Before we worry about reinventing JSON parsing; I use JSON.NET for it, I'm not writing my own. I'll reinvent a few things; that's not one of them. I don't use all of the "make it quick" features because they are aligned with a data centric view. If we want to be doing Object Oriented Programming, we MUST... MUST! interact with objects.
OK, let's get to the focus of the post - How do we return the data?
There's kinda two questions here, serialization and a model. They have similar answers. Let's look at the first one!
An outgoing webrequest
I'm going to assume this is an API of some fashion returning a json string.
What's the behavior? What do we want to have happen?
I VERY heavily ascribe to looking at just the behavior of the system for it doing what I want. Not much into the implementation details, within the constraints of the MicroObject practices. These practices make the code maintainable; so I keep those. It does include "No Getters" so we can't use the default annotations to use the auto serializers.
It depends on where we're strarting from. If we're in a legacy system; I'd deal wtith this level last. Use discipline to remove all use, and continue to let the tool use the pieces.
public class WithPublicProperties
{
public string Value1 { get; set; }
public string Value2 { get; set; }
}
[TestMethod]
public void PublicPropertiesSerialized()
{
WithPublicProperties withPublicProperties = new WithPublicProperties {Value1 = "MyName", Value2 = "SomethingElse"};
string actual = JsonConvert.SerializeObject(withPublicProperties, Formatting.None);
Assert.AreEqual("{\"Value1\":\"MyName\",\"Value2\":\"SomethingElse\"}", actual);
}
Is this the best state? No. If the properties aren't used anywhere except JSON parsing... it's better than using the raw data allover.
Because it's good to demonstrate our failures... I tried the same class/test as above with private properties; Newtonsoft didn't serialize them... yay? I thought it might.
Preferred way
My preferred way is to answer, "What's the behavior?". The answer is - Serialization. We want the object to be serialized for transmission across the wire.
How do we interact with a class when we want a behavior?
A method.
This was mentioned in the twitter comment. Instead of "ToMap", I'd use a method named for what we want - Serialize
. What's the full signature? Whatever works best. The one I use most often is void Serialize(ISerialization serialization)
. This enables the caller to determine the actual form of serialization. JSON, XML, BSON... whatever - It just needs to implement your ISerialization
and it's all good.
In my C# world, this is usually an adapter/facade to the Newtonsoft JObject.
public sealed class NewtonsoftJsonSerialization : ISerialization
{
private readonly JObject _jObject;
public NewtonsoftJsonSerialization() : this(new JObject()) { }
private NewtonsoftJsonSerialization(JObject jObject) => _jObject = jObject;
public void Add(string key, string value) => _jObject.Add(key, value);
public void Add(string key, int value) => _jObject.Add(key, value);
}
The class that needs to serialize does something like this:
public interface IMySerializable
{
void Serializable(ISerialization serialization);
}
public sealed class WithSerialization : IMySerializable
{
private readonly string _val1;
private readonly string _val2;
public WithSerialization(string val1, string val2)
{
_val1 = val1;
_val2 = val2;
}
public void Serializable(ISerialization serialization)
{
serialization.Add("Value1", _val1);
serialization.Add("Value2", _val2);
}
}
I don't have "ONE WAY" to do this. I have a number of examples from previous projects; but that's what THAT code needed. This code might need something different. I'll use them as guides, but I'll re-implement these classes in every project that needs them. It's so easy to re-create the adapters to exactly how the code needs it, any generalization isn't worth it. You'd just end up with the full API of JObject. (I do cheat and copy/paste w/Edits. I'm lazy)
Serialization becomes recursive. If it's not raw data, we call Serialization
on the object we hold. Keep doing that until every object has serialized themself; then you have a full serialization.
This gives the control back to the object. If we want any data manipulation prior to serialization - How do we do that with any of these tools AND NOT in for the code? When the object controls it's own serialization; done and done - It simply doesn't.
I can hear the shouting about annotations and attributes - Yes. Then the class is tightly coupled to whatever JSON tool you use. If you also serialize to XML; then the XML tool; and BSON... It never ends. Your class becomes so complicated because your endpoints need something? Sounds like a vast violation of good architecture. :)
Let the object serialize itself.
There is some additional complexity this way. For databags; it doesn't make much sense. For ACTUAL objects; they normally have no access to the data to be serialized; or the resulting JSON is nested and more complex than it should be. Your JSON definition becomes coupled to your code's class coupling structure... how messed up is that? And naming; unless you start to tightly couple to your serialization tool...
Letting the object serialize itself decouples from the serialization tool. The tool is destructive when it's invasive in the code.
OK - Next Question
To the Controller
First - I think MVC is horrible. Any of these Model/Controller patterns are ANTI-OOP. OOP doesn't use models. Classes ending in '-er' are an anti-pattern. These UI patterns FORCE us to write non-object oriented code. Which works great when you're not writing good OO.
Other ways to interact with the UI were some of the early thoughts I wrote for the blog The Hotel Pattern. This came from a MASSIVE mis-understanding of the MVVM pattern. But I liked it. I don't use it. It's better than MVC (IMO), but ... not stricly what I ended up with in the Premera Windows Store app. Similar though. I write about what I think the UI layer should do here - Hint: It should do nothing. :) That's what my UI did for the Premera App.
I know that most of the software world is hooked on these anti-patterns for the UI (that's right, I said anti-pattern.) They work GREAT for bad OO code that involves databags. Especially databinding... which you should never do.
BUT - That being said - We don't always have a choice. We need to play nice with the UI side and we want our code to be good object oriented code... what do we do?
We serialize. wait... we're in terrible controller land... we model.
It's back to behavior. What do we want to accomplish? Having a databag representation of our object. Let the object control the behavior we want. The behavior we want is to generate a databag representation of our object. Let the object do it.
Another key is that the databag is a REPRESENTATION of the object; not the object itself. When we blend the data representation with the abstract object, the system becomes complicated. It's harder to understand, use, and maintain. Decoupling the data representation from the behavior representation help kepe the complexity of the system down.
public sealed class ADatabag
{
public string UiNamingValue { get; set; }
public string DecoupledNames { get; set; }
}
public sealed class AGoodObject : IToModel<ADatabag>
{
private readonly string _val1;
private readonly string _val2;
public ADatabag ToModel() => new ADatabag{UiNamingValue = _val1, DecoupledNames = _val2};
}
public interface IToModel<out T>
{
T ToModel();
}
This does add abstraction to the system. Abstraction is how we protect components from change elsewhere. If it makes sense to change the name of the field the UI interacts with; we modify ADataBag
. Our tools will update AGoodObject
. No where else in the system has to know. Only 2 files change. Our abstraction keeps the our scope of change exceptionally narrow. I don't think it could be any narrower.
The abstraction, the decoupling, from what the UI interacts with frees the UI side to make changes as appropriate without the need to worry about the impact to the reset of the system.
Summary
I hope that helps demonstrate some of the ways I approach providing the data to exits the 'system'.