At Don’t Panic Labs, we care a lot about building software that can survive the test of time. We don’t care to build software that has to be thrown away after a couple of years. That focus on building sustainable software allows us to help deliver on “enabling sustainable business agility.”
The basic principle from which all other principles originates is the concept of information hiding which, in its most basic definition, is the exposing of only a minimal amount of information. This concept was first described by David Parnas in 1972.
Another key concept that derives directly from information hiding is the practice of software coupling. Coupling refers to how the modules / services within a system interact.
A large part of building sustainable software is making sure the software survives business changes or can remain agile. Building software that meets this level is best accomplished by decomposing the system by volatility.
But, just because you design a system with volatility in mind doesn’t mean that you won’t run into problems later. A good design is an essential starting point. Without a good design, we will be in a continual process of refactor, refactor, refactor.
Part of a good design is the boxes and arrows. Boxes and arrows often create a faster way to describe a system, then looking at the source code. Boxes and arrows give us the ability to review a design quickly and review many aspects of our design without having to write a line of code. If any bad coupling exists, we should be able to quickly see it.
When designing our solutions, we have to keep many principles in mind at the same time. The classic software engineering principles of information hiding, SOLID, high cohesion, separation of concerns, and coupling. We need to create solutions that achieve those goals. The sooner we take these into account, the better.
In this post, we are going to focus on applying these principles in two ways. The first is on the contracts themselves. The second is on the data passed between the contracts.
Contracts define the actions performed by a service. A Contact Manager service might have a few operations, such as Find and Save of a Contact.
- Odd grouping of methods
- Multiple methods to complete a single action
- Temporal coupling
- Simply-described methods
- Make it look like you are calling a remote service
Data contracts define the data communicated between services. Sometimes we refer to data contracts as DTOs. As developers, we often think about the contracts, but often completely forget to think through the data contracts.
Data Contract Design
- Exposing too much information
- Don’t return too much
- Don’t accept more than you need
- Simple data types
- Data that can be serialized
There is no right way to do things all of the time. Engineering is all about tradeoffs. But it is important to know when you are making tradeoffs.
Let’s walk through a couple examples of contract design decisions. We often prefer one say to solve these problems, but that doesn’t mean these recommendations should be assumed to always be correct. You can often come up with situations where the opposite is true.
One problem we often see is the creation of too many methods that must be called in a specific order to complete an operation. If we do a bad job decomposing our Contact Manager service, we could end up with a client that needs to make five calls just to complete a single operation.
A better version would be to have a single AddContact method on our ContactManager.
Now let’s assume you do everything right with your contract design. While you can decompose into the right number of services, you can still create some coupling problems. Probably the most common way of getting into this bad scenario is coupling everything to your Entity Framework models. By doing this, you may be unable to make database changes without also updating your user interface.
We can solve this problem by creating multiple DTOs, ideally with each layer returning its own DTO.
A common problem with data contract design is accepting more than you need. Instead of accepting the properties / object that you need, you will accept more than you need. In the example below, the Business DTO contains a Contract property. If the Save business method does not use the Contacts property, then we shouldn’t have it on our DTO. If it is passed in, it should be used.
If we can create a service that accepts too much data, we can also create a service that returns too much. A common issue is returning properties that are not needed. A common reason for this is the property exists in the Entity Framework model, and we just blinding bring it along in our DTO. Don’t bring the data along unless it is really necessary.
Design doesn’t stop when we get done drawing boxes and arrows, it continues all the way into the code. We need to be making careful decisions all along the way. The decisions we make with our contracts and data contracts are just as essential to think through. Hopefully, this blog post will give you some insight into that decision-making process.
David Parnas, “On the Criteria To Be Used in Decomposing Systems into Modules”
Steve McConnell, “Code Complete”
Robert Martin, “Agile Principles, Patterns and Practices in C#”
Robert Martin, “Clean Architecture”
If you’re interested in a deeper dive on this and other related topics, check out our Software Design and Development Clinics.
To keep up with us, subscribe to our bi-monthly newsletter.
If you have any questions, leave a comment below or hit me up on Twitter at @chadmichel.