QR code

Yet Another Evil Suffix For Object Names: Client

  • Odessa, Ukraine
  • comments

OOPoop

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: Client.

Sin noticias de Dios (2001) by Agustín Díaz Yanes
Sin noticias de Dios (2001) by Agustín Díaz Yanes

Let me give an example first. This is what an object with such a suffix may look like (it's a pseudo-code version of the AmazonS3Client from AWS Java SDK):

class AmazonS3Client {
  createBucket(String name);
  deleteBucket(String name);
  doesBucketExist(String name);
  getBucketAcl(String name)
  getBucketPolicy(String name);
  listBuckets();
  // 160+ more methods here
}

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 HttpClient from 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 AmazonS3Client at the time of writing, while it started with only a few dozen just a few years hundred 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: S3Object, ObjectMetadata, BucketPolicy, 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 re-used 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 Bucket, Object, Version, Policy, etc. Each of them exposes the functionality of real buckets, objects and versions, which the AWS S3 can expose.

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.

BTW, check out some of the JCabi libraries (Java) for examples of object-oriented clients without "client" objects: jcabi-github, jcabi-dynamo, jcabi-s3, or jcabi-simpledb.

sixnines availability badge