Some time ago, we were talking about "-ER" suffixes in object and class names. We agreed that they were evil and must be avoided if we want our code to be truly object-oriented and our objects to be objects instead of collections of procedures. Now I'm ready to introduce a new evil suffix:
All "clients" look similar: They encapsulate the destination URL with some access credentials and expose a number of methods, which transport the data to/from the "server." Even though this design looks like a proper object, it doesn't really follow the true spirit of object-orientation. That's why it's not as maintainable as it should be, for two reasons:
- Its scope is too broad. Since the client is an abstraction of a server, it inevitably has to represent the server's entire functionality. When the functionality is rather limited there is no issue. Take
HttpClientfrom Apache HttpComponents as an example. However, when the server is more complex, the size of the client also grows. There are over 160 (!) methods in
AmazonS3Clientat the time of writing, while it started with only a few dozen just a few
yearshundred versions ago.
- It is data focused. The very idea of a client-server relationship is about transferring data. Take the HTTP RESTful API of the AWS S3 service as an example. There are entities on the AWS side: buckets, objects, versions, access control policies, etc., and the server turns them into JSON/XML data. Then the data comes to us and the client on our side deals with JSON or XML. It inevitably remains data for us and never really becomes buckets, objects, or versions.
The consequences depend on the situation, but these are the most probable:
- Procedural code. Since the client returns the data, the code that works with that data will most likely be procedural. Look at the results AWS SDK methods return, they all look like objects, but in reality, they are just data structures:
PutObjectResult, etc. They are all Data Transfer Objects with only getters and setters inside.
- Duplicated code. If we actually decide to stay object-oriented we will have to turn the data the client returns to us into objects. Most likely this will lead to code duplication in multiple projects. I had that too, when I started to work with S3 SDK. Very soon I realized that in order to avoid duplication I'd better create a library that does the job of converting S3 SDK data into objects: jcabi-s3.
- Difficulties with testing. Since the client is in most cases a rather big class/interface, mocking it in unit tests or creating its test doubles/fakes is a rather complex task.
- Static problems. Client classes, even though their methods are not static, look very similar to utility classes, which are well-known for being anti-OOP. The issues we have with utility classes are almost the same as those we have with "client" classes.
- Extendability issues. Needless to say, it's almost impossible to decorate a client object when it has 160+ methods and keeps on growing. The only possible way to add new functionality to it is by creating new methods. Eventually, we get a monster class that can't be reused anyhow without modification.
What is the alternative?
The right design would be to replace "clients" with client-side objects that represent entities of the server side, not the entire server. For example, with the S3 SDK, that could be
Policy, etc. Each of them exposes the functionality of real buckets, objects and versions, which the AWS S3 can expose.
The right design would be to replace clients with client-side objects that represent entities of the server side.
Of course, we will need a high-level object that somehow represents the entire API/server, but it should be small. For example, in the S3 SDK example it could be called
Region, which means the entire AWS region with buckets. Then we could retrieve a bucket from it and won't need a region anymore. Then, to list objects in the bucket we ask the bucket to do it for us. No need to communicate with the entire "server object" every time, even though technically such a communication happens, of course.
To summarize, the trouble is not exactly in the name suffix, but in the very idea of representing the entire server on the client side rather than its entities. Such an abstraction is 1) too big and 2) very data driven.