TRIM: Wrapping C# Business Objects with Foundational Concepts
When working with core objects in your system, applying basic concepts makes them easier to use. Today, I introduce a simple acronym to make your objects more maintainable.
As a software developer, you see a lot of systems (legacy and greenfield) take shape and start to morph into something different over time.
Whether they are monoliths or lean, niche apps, they take on certain characteristics, or patterns, to accomplish a business need.
We all know (or should know) about onion architectures where business objects are considered the core of your application. If we move out to the 10,000-foot view, we can derive one or many number of concepts that appear in these business objects almost every time no matter what application you write.
As a web architect, I've observed a number of applications using these same characteristics in business objects in one way or another, but strewn throughout a system and not consolidated in any way. Consider these business objects as another onion layer.
These concepts wrap around business objects and are considered their own onion architecture at a micro-level.
I'm starting to refer to these business object "mini-layers" using the acronym TRIM.
Since we're so close to Christmas, think of this concept like Christmas trim.
You have the Christmas tree (your business objects) and then you have the extra decorations like tinsel, ornaments, lights, a star, etc. The trim is considered these four ideas; extra stuff to make the Christmas tree more attractive (seriously, who wants a naked Christmas tree, Charlie Brown?) ;-)
These four concepts, especially with microservices, make your objects more flexible to your system and dresses them up a bit providing more functionality to your application.
Overview of TRIM
Concept: Serialization/Deserialization, LINQ-to-XML, Adapter Design Pattern.
While we can use business objects just as they are, some systems do not interact well with an innate business object. Therefore, business objects must be transformed into something more meaningful and useful to prove their worth.
This concept is used extensively throughout software development especially more recently with microservices.
- With the amount of web APIs on the Internet, these interfaces receive data and transform them into DTOs, entities, or something useful to a system using serialization/deserialization.
- How many times have we created a report? This characteristic transforms the data into something the user can clearly understand. It's not an enumerated type, it's transformed into something a human can read.
- Creating an external resource (like a PDF, XML, or JSON format) would also be included as a transformation.
These are merely a handful of examples, but as you continue writing code, more may appear before your eyes.
Concept: Business Rule engines, hard-coded rules.
With business objects, we require validation when receiving data from the user whether it's after deserializing an object after a web API call or after they input something into a textbox and post the data back to the controller.
Entity Framework has data annotations to validate business object members ([Required], [RegEx], etc.) where others are using or building business rule engines to wrap around the business objects of their system.
These types of libraries are becoming more common in the software industry for two reasons:
- They aren't coupled to a business object
- Can easily be swapped out with other rules engines when one is not supported anymore.
Additionally, business rules for an object could be considered as workflow objects as well. I've seen systems with the following:
If a zipcode is "43082" then [SendAnEmail]
Would this be consideration for a business rule engine or a workflow engine?
Either way, this would keep your business object intact and isolate it.
Concept: Communication with other objects
How do your core objects communicate with each other in your application?
If we look at Entity Framework and work with a database-first approach, we may have a top-level or primary class to access all other objects underneath it. This presents mucho-macho coupling.
Your communication with your objects should follow the Law of Demeter. To give you a better understanding, the basic concept explains that it's bad for one single method to know the entire hierarchy of the system.
For example, instead of writing this:
It would be better to write this:
With this example, the customer class shouldn't care about the entire hierarchical structure of the system, it just wants the customer's state.
The GetState() method could use any number of scenarios in it's implementation. It could drill down using Gets through the system or it could even make an API call to an external resource.
That's the beauty of it. The implementation of your methods and communication between your objects should be hidden.
All it cares about is the return value. It doesn't care how it gets it.
What if the state wasn't part of a hierarchy? What if it was just a simple IEnumerable? If it's composable, it would make it easier to access a state list instead of drilling down into the navigational structure of the system.
Of course, the Law of Demeter is simply a guideline. You may have your reasons why you need to see spots, but lately, I'm personally trying to break the habit.
Concept: Left-to-right assignments; mapping data from one object to another; AutoMapper
The M in TRIM may be similar to the Transformation step, but it's merely transferring data from one object to another and not converting it into something completely different like the transformation stage.
This could be a subset of a god object.
When loading an entity from the database, you receive the entire entity populated. The entity has 15 members in the class and you only want to use 5 for viewing purposes. It's the same object, but you want to create a list of objects mapping those existing properties to a skeleton object to only hold what you need.
This mapping concept could exist with extension methods masking the mapping process (.ToCustomerView(), .ToProductsDto(), etc.) or could even be separate classes to perform the mapping.
AutoMapper is a great example of this. The products' one responsibility is to map properties to another object...and it does it exceptionally well!
Everything Working As One
The whole idea is taking your objects and making them as flexible as possible for your system, but not touching them unless you absolutely have to.
You need a list of products from your Order class for an AJAX call? T = Transform them into JSON objects with an API.
Add a new business rule to a Customer class? R = Add the rule with either a business rules engine or in the class itself.
You want to display Orders for a customer? M = Map the entire list of orders to a smaller subset of orders for display purposes.
Want to get a list of past orders from a customer? I = Use the GetOrders method on the Customer class (which either makes an API call or drills down to retrieve the orders from a table).
As you can see, when you create objects for your application, you may be using one of these concepts to extend your business objects to other enterprise developers or even third-party vendors.
With these four concepts for your business objects, they prepare your system to be as flexible as possible.
In one way or another, most business objects change using one of these four concepts. Applying one (or many) of these characteristics to your objects to easily adjust to any type of given requirement.
Once your business objects are defined, you should see patterns emerging making your system easier to use and maintain.
Think of TRIM when you're "decorating" your business objects this year.
Does this acronym make sense? Did I miss a concept in my list? Should the acronym be renamed? Post your comments below and let's discuss.