Introduction
What is jimmutable?
jimmutable is both a lightweight library and a "way of thinking" that is intended to make the authorship of java code for the cloud easier, more free of mistakes, and, simply, more fun.
Although a general purpose java library, jimmutable is particularly well suited to the writing of cloud (AWS) hosted micro-services.
Key Features
-
Don’t make me think
jimmutable saves you mental energy by laying down simple, easy to follow patterns that are designed to make the error free writing of code easy -
Immutability Made Easy
jimmutable, as the name implies, is patterned around easy to write and maintain immutable java objects -
Easy Unit Tests
jimmutable embraces unit testing at a root level and facilitates the easy creation of unit tests. Of particular importance is the easy creation of unit tests for serialization (often a sore point) -
Easy Backwards Compatibility
jimmutable makes it easy to extend an object while retaining the ability to read "old" versions of the object -
Performance
Speed and low memory footprint are a central part of the design -
Loved by System Administrators
Much thought and consideration is given to the poor souls that must toil in the never ending effort to keep our apps up. We are forever in your debt. jimmutable tries to make your life easier
Can I use jimmutable in my project?
Almost certainly. All of jimmutable is licensed under the 3-clause BSD license. If that is not permissive enough, what is?
So, fork it, port it, use it commercially… basically, have at it!
Where can I get jimmutable?
-
pages
— what you reading right now
https://github.com/jimmutable/jimmutable.github.io.git -
core
— immutability, serialization, threading, exceptions, etc.
https://github.com/jimmutable/core.git
Don’t make me think
"Don’t make me think" is one of the core tenants of jimmutable. The theory goes something like this: each day we wake up and greet the world. At this moment of waking, we posses only a finite amount of mental energy. Think of it like a book of matches. The solution of each and every mental problem we confront during the day burns one of these matches. When our matchbook is empty, we are done. Time to go fishing, play golf, go for a walk, anything but more mental effort. Trying to work more after our matchbook is gone is just like trying to start a fire with no matches — you can do it, but its way easier with matches.
If you accept this state of affairs as being consistent with the human condition (as this author does) than one thing you can do to make your life as a programmer better is to write your code in a way that does not constantly present you with mental puzzles.
How does this happen? I think a simple example will help shed light on things. Imagine, if you will, a function whose purpose is to pretty print (neatly indent etc.) XML. Outside of jimmutable, one common method signature for such a function might be:
String prettyPrintXML(String unformatted_xml)
What is so hard about that? Well, let me ask you something, based on that method signature, what does prettyPrintXML do if the input is null? What does it do if unformatted_xml
is not a valid XML document? Does it throw an unchecked exception? If so, which one(s)? Does it simply return its input? It is, actually, a touch of a puzzle.
Now, sure, you can go and read the JavaDoc for the function. Maybe the author will have been good enough to clearly document the error conditions. Maybe not, so… ctrl click in eclipse and onto the source code… DOH! I don’t have the source jar installed. Ok… ummm… well, lets write a short little program to test it… BLAM! One match burned.
How to avoid thinking in jimmutable
jimmutable is designed to be used "in the heat of battle". You know, you have your tunes blaring in your headphones and you are cranking out code. You are getting things done! You don’t have time to check no stinking error conditions, you have sh!t to do! jimmutable is great for this because code written in this style is written with a commitment never to make you think. This is achieved by always following a pattern. One of jimmutable’s patterns is that the error behavior of a method will always be trivially understandable from its method signature (which you can’t help but see from autocomplete in eclipse while you are pounding klocs (thousands of lines of code) out.
-
Method signatures that tell you (most) everything you need to know about how a function works
-
Throw unchecked exceptions when the truly unusual happens
-
Immutable objects make life easy and code safe
Method signatures in jimmutable
In jimmutable, if you see a method signature like this:
String prettyPrintXML(String unformatted_xml)
Then it means: do you worst. No matter what you give me as an argument: null
, JavaScript, the complete text of war an peace, whatever, I will work and give you the right answer. I hope it is immediately obvious to you, dear reader, that the prettyPrintXML
function does not have a snowballs chance in hell of being this rugged. As a result, it has a method signature of
String prettyPrintXML(String xml, String default_value)
(You can see it yourself, it is a real function in JavaCodeUtils
)
What does a signature like this mean? It means, give me anything you want for xml
. I will try to pretty print it. If I can’t make this happen (hey, I can’t pretty print the text of war a peace) I will return default_value
. I won’t throw an exception (checked or unchecked). I won’t print a bunch of a garbage to System.out. I will, simply, return default_value
Conventions for getters
Imagine that you have a jimmutable class, Book
. This is shockingly easy to imagine as there is, in fact, a Book
class implemented in org.kane.base.examples
, but I digress. A Book
has several fields, one of which is title. As we might expect, Book
implements a method to get the title. Its signature is as follows:
String getSimpleTitle()
Whenever you see a getSimpleXXX
in jimmutable, you know a couple of things:
-
A non-null value will always be returned
-
Nothing too bad can happen (no exceptions, infinte loops, many seconds of computation, etc.)
Now, some books have an ISBN (International Standard Book Number) (Yes, virginia, there are books in the world that do not have a ISBN). The method to get ISBN, therefore, looks like this:
String getOptionalISBN(String default_value)
You probably have already stopped thinking, and this is a good thing! You know, intuitively, what this means. For the record, I will share with you that, whenever you see getOptionalXXX
in jimmutable:
1.) The field you are getting may be set, or it might be unset
2.) If the field is set, then the function will, trivially, return the value to you
3.) If the field is not set, then the function will return default_value
to you
4.) The function won’t take any significant amount of resources to execute, and won’t do anything too bad (throw an exception etc.)
Complicated functions
Ok, so, what if I want to write a function that is truely complicated. Something like, for a book, examine the text and tell me if the book is in iambic pentameter (note: for the record, I have no idea how to implement this function). In that case, the function signature will have the word Complex
in it. For example:
Boolean getComplexIsIambicPentameter(Boolean default_value)
or
Boolean computeComplexIsIambicPentameterByExaminingProse(Boolean default_value)
What do we know when we see Complex?
1.) This function requires some thought
2.) It might take a while to run
3.) It might not work — when it does not work, it will return default_value
Dealing with the truly unusual
As we merrily code along, we often find ourselves thinking "Should I check for …" followed by "nah, only someone who is truly braindead would do that. For example, we all know that we should validate every input to every function we write. Because… Murphy’s law. Sometimes this flows very naturally. For example, imagine that you are writing a function that reads small files from disk, returning the files bytes. It is pretty obvious that a good method signature for this might be:
byte [] getFileBytes(File src, byte default_value[])
And this is all pretty natural. If src
happens to either not exist or not be a file, return default_value
and all is well. This also gives us a natural out for (a truly weird) case: src
is null.
Now imagine, dear reader, that you are coding a new class BookUtils
and it implements a function that checks to see if the title of a book is a palindrome (the same spelled forward or backward) (For example, Seveneves by Neal Stephenson, ISBN 0062334514)
As you start to code this function, all seems well…
static public boolean isTitlePalindrome(Book book)
{
String title = book.getSimpleTitle();
String title_backwards = new StringBuilder(title).reverse().toString();
return title.equals(title_backwards);
}
Ahh, but now you have a problem. What if, just what if, some jerk decides to pass in a null
book. This is, to put it mildly, super crazy unlikely. Its probably only going to happen in development etc. and its going to make our code dumb to always have to have a default value etc. for such an edge case. Don’t worry — in this case, jimmutable uses unchecked exceptions to "get those jerks right back". Here is how you code this:
static public boolean isTitlePalindrome(Book book)
{
Validator.notNull(book);
String title = book.getSimpleTitle();
String title_backwards = new StringBuilder(title).reverse().toString();
return title.equals(title_backwards);
}
The jimmutable unchecked exceptions
-
ValidationException
Throw aValidationException
whenever something is "not valid". Frequently thrown when a function is passed invalid parameters -
SerializeException
Throw when serialization goes bad. Unable to fully read a file full of objects? Network connection fails in the middle of downloading a file? Throw aSerializeException
If serialization works, but the underlying data is not valid, throw aValidationException
-
ImmutableException
Thrown when someone tries to change something after an object has been completed
Standard immutable objects
By default, objects in jimmutable are robust and well behaved. Any object that extends StandardImmutableObject
:
-
is immutable (after construction)
-
is serializable to and from XML
-
is serializable to and from JSON
-
is deep-clonable (simply call
deepClone
) -
has robust support for data normalization
-
is checked for data validity every time an object is created/de-serialized
-
has a proper implementation of
hashCode
-
has a fast implementation of
equals
-
has a fast implementation of
compareTo
-
is easy to write a unit test for (see the various unit test examples and the
toJavaCode
function)
This helps you avoid thinking on several fronts:
-
Is this object thread safe? Of course… its immmutable
-
Is it safe to return a reference to this object? Of course… its immutable
-
Can I add this object to a
Set
?HashSet
?TreeSet
Of course… -
Can this object be the key of a
Map
? Of course! -
Can I duplicate this object? Yes! But no real need… its immutable…
Objects are either abstract
or final
In jimmutable, any class you make should either be abstract or final. This saves you a whole ton of mental energy.
How? Well, lets put it this way, designing classes that can safely be extended is super difficult. Oh, sure, you may think you have, without thinking about it, made a class that is trivially extendable… but you probably have not. Is it thread safe? Can it serialize/de-serialize? Does it have weird construction errors? What happens when someone extends it that does not know what the heck they are doing?
It is, basically, impossible, to create on unintentionally. Therefore, we save ourselves from having to think about this by making it explicit: if we are designing a class that can be extended we declare it abstract
. Otherwise, it is final
.
I can hear the objections already: this breaks OOP! I can see the examples: Shape
, Rectangle extends Shape
, Square Extends Rectangle
. To which is say: no, its easier to have Shape
, AbstractRectangle extends Shape
, final Rectangle extends AbstractRectangle
, final Square exetends AbstractRectangle
. Trust me.
Immutable objects in jimmutable
So, you want to make a new immutable object in jimmutable. Easy, either extend StandardImmutableObject
or Stringable
StandardImmutableObject
vs Stringable
You should extends Stringable
whenever you are creating a class that is easily serialized to (and de-serialized from) a String
. For example, see BrandCode
and/or PartNumber
in jimmutable’s examples. Both simply "wrap", and limit the allowable contents of, a String
. Therefore, they extend Stringable
.
BTW: Stringable
iteself simply extends StandardImmutableObject
. So… all it really does at the end of the day is save you some typing. That being said, please use Stringable
when applicable.
The lifecycle of an immutable object
Every immutable objects starts life as a mutable one. When you think about it, this simply must always be the case — otherwise, how would you set any of the fields during construction? Outside of the construction chain, however, every StandardImmutableObject
is immutable. This means that, no matter what you do, it is not possible to obtain a reference to a StandardImmutableObject
that is not immutable.
How does an object make the transition from mutable to immutable? Simple: a call is made to complete()
complete()
performs the following actions, in exactly the following order:
-
normalize()
-
validate()
-
freeze()
When you need to call complete
vs when complete
is called for you
If you create your own, non serialization constructor, you need to (once you are done modifying the object being built) call complete
yourself.
When objects are created via de-serialization (i.e. StandardObject.deserialize
), then complete
is called for you. Since your de-serialziation constructor (the constructor that takes an ObjectParseTree
as its only parameter) is invoked as part of the de-serialization process, you do not need to call complete in this constructor (indeed, that is a big no-no)
When an object is cloned (deepClone
) it is serialized and de-serialzied, therefore complete
is called for you.
Because our standard builder pattern (more on this later) uses deepClone
, there is no need to call complete
when building an object using a builder (it is called for you via deepClone
)
Virtualized Backplane
To aid in development for Jimmutable apps, we utilize a virtualized intance of the backplane. The backplane consists of Elasticsearch, Kibana, and Redis, and all of these live on a VM using VirtualBox. This allows us to simply deploy an image and start up the VM to be ready to develop web applications, rather than having to create local installations and maintain versions on an individual basis.
List of Installed Services on Image
-
Amazon Linux 2 AMI
-
Java 1.8
-
Elasticsearch 7.6.1 (port 9200 and 9300)
-
Kibana 7.6.1 (port 5601)
-
Redis 4.0.14 (port 6379)
All running on local host (127.0.0.1)
Step 1: Enable Virtualization On Your Computer
In order for VirtualBox to be fully operational, your computer must be setup to allow for virtualization. If your computer is already set up to do so, feel free to skip this step.
Windows PCs
You will need to configure your processor in the BIOS to allow for virtualization. Open the BIOS and find “Intel Virtualization” and enable it. Save your changes and boot up your computer as normal.
Mac
Virtualization support for your processor should be enabled by default. Skip this step if that's the case, otherwise use this handy guide
Step 2: Download and Install VirtualBox
Visit VirtualBox's download page and download the latest VirtualBox client for your OS. When running through the installer, choose all the default options.
Step 3: Download the Virtual Backplane Image
We've created a VirtualBox VM Image (.OVA) file to use for the VM. Download it here. Be careful, it's big (~1.3 GB).
Step 4: Import and Start the VM in VirtualBox
In VirtualBox, click File → Import Appliance. Select the image that you downloaded in Step 3. Leave the default options for the import process. Once completed, it should show up in the list of VMs available. Start it using the Start button (Alternatively, choose Normal Start).
A new window should appear with your Linux instance. Select the latest Amazon AMI build, and advance to the login screen. Your VM is now up and running. All services (Elasticsearch, Kibana, and Redis) have been configured to start on boot, and port forwarding allows them to be used as localhost instances. So your backplane is up and ready to go!
Step 5: Launch your Web App
When working with Elasticsearch, Kibana, and Redis, Jimmutable applications default to localhost and the associated ports (9200/9300, 5601, and 6379 respectively). This is what the VM has been configured for as well.
Launch your web application, and it should attempt to connect or use these services as if they were running on localhost.
Jimmutable Cloud currently uses the Elasticsearch Java High Level REST client. Be aware that the current client does not support the previous version (6.x) of Elasticsearch
Extra Debugging Info
Login
-
username: ec2-user
-
password: password
Operating the shell
Service data locations
-
/usr/share/elasticsearch/
-
/usr/share/kibana
-
/usr/local/bin (Redis)
To modify configs
-
sudo nano /etc/elasticsearch/elasticsearch.yml
-
sudo nano /etc/kibana/kibana.yml
-
sudo nano /etc/redis/redis.conf
-
Be sure to restart the respective service
To restart services
-
sudo systemctl restart elasticsearch
-
sudo systemctl restart kibana
-
sudo systemctl restart redis
-
Alternatively, restart the whole VM
To check service status
-
sudo systemctl status elasticsearch
-
sudo systemctl status kibana
-
sudo systemctl status redis