I'm a big fan of Amazon Web Services (AWS). I'm using them in almost all of my projects. One of their most popular services is Simple Storage Service (S3). It is a storage for binary objects (files) with unique names, accessible through HTTP or RESTful API.
Using S3 is very simple. You create a "bucket" with a unique name, upload your "object" into the bucket through their web interface or through RESTful API, and then download it again (either through HTTP or the API.)
Amazon ships the Java SDK that wraps their RESTful API. However, this SDK is not object-oriented at all. It is purely imperative and procedural—it just mirrors the API.
For example, in order to download an existing object
doc.txt from bucket
test-1, you have to do something like this:
As always, procedural programming has its inevitable disadvantages. To overcome them all, I designed jcabi-s3, which is a small object-oriented adapter for Amazon SDK. This is how the same object-reading task can be accomplished with jcabi-s3:
Why is this approach better? Well, there are a number of obvious advantages.
S3 Object is an Object in Java
S3 object get its representative in Java. It is not a collection of procedures to be called in order to get its properties (as with AWS SDK). Rather, it is a Java object with certain behaviors. I called them "ockets" (similar to "buckets"), in order to avoid clashes with
Ocket is an interface, that exposes the behavior of a real AWS S3 object: read, write, check existence. There is also a convenient decorator
Ocket.Text that simplifies working with binary objects:
Now, you can pass an object to another class, instead of giving it your AWS credentials, bucket name, and object name. You simply pass a Java object, which encapsulates all AWS interaction details.
Extendability Through Decoration
For example, you want your code to retry S3 object read operations a few times before giving up and throwing an
IOException (by the way, this is a very good practice when working with web services). So, you want all your S3 reading operations to be redone a few times if first attempts fail.
You define a new decorator class, say,
RetryingOcket, which encapsulates an original
Now, everywhere where
Ocket is expected you send an instance of
RetryingOcket that wraps your original object:
foo.process() won't see a difference, since it is the same
Ocket interface it is expecting.
Again, due to the fact that all entities in jcabi-s3 are interfaces, they are very easy to mock. For example, your class expects an S3 object, reads its data and calculates the MD5 hash (I'm using
DigestUtils from commons-codec):
Here is how simple a unit test will look (try to create a unit test for a class using AWS SDK and you will see the difference):
As always, your comments and criticism are welcome as GitHub issues.