Cats vs dogs.
I am a dog person. You could say I myself am needy for attention, but I like to think I enjoy the aggressive love of a dog. Our family dog constantly forces himself onto my lap while I am reading a book. Dogs really, really want to be included.
Now I have nothing against cats. Cats are great too. They have their own personality. They tolerate us as part of their world. That doesn’t make cats wrong, just different.
I bring this up because it reminds me of objects and services.
Both objects and services have their advantages. I believe you can create great software using either. But I believe most software engineers are more likely to succeed if they live in a services world.
Hold on a minute. Are you saying don’t use object-oriented programming (OOP)? Kind of.
I am not saying you must use a programming language that doesn’t support OOP. You can do service-oriented programming in almost any language. We use C#, which fully supports OOP.
Maybe before going on, I should describe SOA (services) vs OOP (objects). SOA uses services to build systems. OOP uses objects to build systems, and it tends marry data and behavior. Services tend to separate data from behavior. In an SOA, the separation between data and behavior is often obvious.
SOA = Well-defined operation contract. Separate data from behavior. Services are the building blocks.
OOP = Objects are the merger of data and behavior. Objects are the building blocks.
OOP languages need to have four features. First, the ability to create objects. Second, the ability to structure code through inheritance. Third, the encapsulation (ability to hide some data). Forth, polymorphism (the ability to change the way a method behaves).
OOP systems tend to couple data and behavior. They also tend to rely upon inheritance to extend and build systems. I personally find inheritance to be a difficult way to structure software and, more importantly, a difficult way to structure readable code. Inheritance seems to work out in simple dog vs cat examples, but often not as cleanly in the real world. We do use inheritance, but we are very careful when we choose to use it. We always try to keep the maximum inheritance levels to under three.
Why do I feel that services are better? I can think of four concrete ways:
- Modules better represents building blocks for systems. I think we all dream of systems that feel like Lego blocks. Systems where each piece can be fit together to build powerful systems. Modules were first described in 1972 by David Parnas, and it is still a concept we search to have implemented today. Modules are basically services in our SOA world. Modules have rigid contracts. In our SOA world, services live behind interfaces in C#. We always program to an interface, not to a real class. This is basically the L in SOLID (Liskov Substitution). We also always program using dependency injection, which is the D in SOLID. Now you can do modular programing using OOP, but I think that doing so in an SOA world is almost a default, not an option. This basically keeps us in the pit of success.
- Services tend to be easier to work with because of separation of data and behavior. The separation of data and behavior is more important than it seems. Keeping data separate makes unit testing your services easier. Instead of building a ton of temporal coupling into your services, your services are usually stateless. Testing becomes data in and data out. Now unit testing isn’t as important as many make it out to be, it is an okay way to improve your systems quality. But if you want quality, you probably need to layer many practices. The great benefit of unit testing is design quality. A system that can be unit tested probably has a better design. If you can test your software, you are probably 50% of the way to good system. Avoiding the coupling of data and behavior helps to facilitate that. Separation of data and behavior also enables you to take advantage of many great scaling options in the cloud.
- Services better represent the way the web works. We are all building distributed systems nowadays. Years ago, a system could live as a big monolith. But today we are building systems that are made up of many distributed parts that communicate with each other by sending messages (data) back and forth. We don’t send behavior over the wire, we send data. Services are a better representation of that world, which makes the mental model closer to reality and hopefully easier for developers to understand.
- Services are easier to change over time. Services make it is easier to create pieces that have only one reason to change. If you were to create an Order object in an OOP system, that object will probably have many reasons to change. The Order object will change depending upon database changes. It will change if we change how we manage fulfillment. It will change as we update notifications. Anytime we touch an order, the Order object will also need to change. In a service based system we could break those individual workflows into their own individual services. We could have an order persistence service, an order fulfillment service, and a notification service. Each of those services would only have one reason to change (the S in SOLID).
Services can still experience many of the same problems as objects. With services, you are still programming in an object-oriented language but you can still create some crazy inheritance if you want to. With services, you can still put code in your data contracts (nothing stopping you), although doing so should feel wrong in a services world. We enforce the “no code in data contracts” pattern during code reviews. I do wish C# had something like a class / struct, but didn’t allow methods.
Services don’t necessarily solve the inheritance problem. Since we code in C# you can still create lot of painful inheritance. Again, discipline is necessary to ensure we don’t go off the rails.
Now you might be thinking that this whole discussion is a distraction. OOP is a programming paradigm, but SOA is a system architecture. I do think you can mix the two. You can use SOA at your endpoints, and use OOP for your code that lives beneath the services. I do think keeping to just a service paradigm makes sense. Instead of writing your data access using an ActiveRecord pattern, consider using a repository pattern where you just pass data to the repository.
If you want to discuss software architecture, drop me an email at firstname.lastname@example.org.
If you are interested in learning more, check out our Software Design and Development Clinic.
If you wish to discuss dogs vs cats, I can be found on Twitter at @chadmichel.