Friday, 9 October 2015

Chronicle-Wire Tutorial (Part 3): Serialising Code

Before reading this, Part 3 of the Chronicle-Wire tutorial, I'm assuming that you are comfortable with at least Part 1 (the basics) of the tutorial.

So far we've looked at how to serialise data as objects as well as serialising data in the form of documents. Now we're going to look at how we can serialise code.

There are two ways this can be done with lambdas and with enums.

Serialising code with lambdas

One of the coolest features of Java 8 are lambdas which provide a means to pass functions (essentially code) from one part of your application to be run in other parts of your application.

Being able to serialise code is really useful, for example, if you want to write some code on a client which gets executed on the server. Or, for map reduce type problems where you break up a problem and distribute the code so that it can be run on many machines. 

This example demonstrates how this can be done, in this case using the class SerializableFunction. The lambda String::toUpperCase is serialised to Bytes using both TextWire and BinaryWire.  The Bytes are then deserialised into a Function which is applied to a string "hello world".



The output of this program is:

----------Testing with TextWire--------------------
Text Wire representation of serialised function
toUpperCase: !SerializedLambda {
  cc: !type net.openhft.engine.chronicle.demo.WireDemoLambdas,
  fic: net/openhft/chronicle/core/util/SerializableFunction,
  fimn: apply,
  fims: (Ljava/lang/Object;)Ljava/lang/Object;,
  imk: 5,
  ic: java/lang/String,
  imn: toUpperCase,
  ims: ()Ljava/lang/String;,
  imt: (Ljava/lang/String;)Ljava/lang/String;,
  ca: [
  ]
}

hello world -> HELLO WORLD

----------Testing with BinaryWire--------------------
Binary Wire representation of serialised function
00000000 CB 74 6F 55 70 70 65 72  43 61 73 65 B6 10 53 65 ·toUpper Case··Se
00000010 72 69 61 6C 69 7A 65 64  4C 61 6D 62 64 61 82 1E rialized Lambda··
00000020 01 00 00 C2 63 63 BC 31  6E 65 74 2E 6F 70 65 6E ····cc·1 net.open
00000030 68 66 74 2E 65 6E 67 69  6E 65 2E 63 68 72 6F 6E hft.engi ne.chron
00000040 69 63 6C 65 2E 64 65 6D  6F 2E 57 69 72 65 44 65 icle.dem o.WireDe
00000050 6D 6F 4C 61 6D 62 64 61  73 C3 66 69 63 B8 34 6E moLambda s·fic·4n
00000060 65 74 2F 6F 70 65 6E 68  66 74 2F 63 68 72 6F 6E et/openh ft/chron
00000070 69 63 6C 65 2F 63 6F 72  65 2F 75 74 69 6C 2F 53 icle/cor e/util/S
00000080 65 72 69 61 6C 69 7A 61  62 6C 65 46 75 6E 63 74 erializa bleFunct
00000090 69 6F 6E C4 66 69 6D 6E  E5 61 70 70 6C 79 C4 66 ion·fimn ·apply·f
000000a0 69 6D 73 B8 26 28 4C 6A  61 76 61 2F 6C 61 6E 67 ims·&(Lj ava/lang
000000b0 2F 4F 62 6A 65 63 74 3B  29 4C 6A 61 76 61 2F 6C /Object; )Ljava/l
000000c0 61 6E 67 2F 4F 62 6A 65  63 74 3B C3 69 6D 6B 05 ang/Obje ct;·imk·
000000d0 C2 69 63 F0 6A 61 76 61  2F 6C 61 6E 67 2F 53 74 ·ic·java /lang/St
000000e0 72 69 6E 67 C3 69 6D 6E  EB 74 6F 55 70 70 65 72 ring·imn ·toUpper
000000f0 43 61 73 65 C3 69 6D 73  F4 28 29 4C 6A 61 76 61 Case·ims ·()Ljava
00000100 2F 6C 61 6E 67 2F 53 74  72 69 6E 67 3B C3 69 6D /lang/St ring;·im
00000110 74 B8 26 28 4C 6A 61 76  61 2F 6C 61 6E 67 2F 53 t·&(Ljav a/lang/S
00000120 74 72 69 6E 67 3B 29 4C  6A 61 76 61 2F 6C 61 6E tring;)L java/lan
00000130 67 2F 53 74 72 69 6E 67  3B C2 63 61 82 00 00 00 g/String ;·ca····
00000140 00                                               ·                

hello world -> HELLO WORLD

Serialising code with enums

Using lambdas to serialise code give the developer maximum flexibility in terms of what can be serialised but there also a couple drawbacks.
  1. The serialised lambda is very bulky (see the print out of the bytes above)
  2. There is no control over what can be serialised.  If you are using serialisation to send messages across the wire between client and server you might want to have more control over the code that can be executed by the client on the server. 
Using enums addresses these shortcomings.

The code below does exactly the same as the code we saw in the lambda example in the first section. It takes the code to transform a string to upper case and serialises it. The code to transform the string is stored in an enum which implements Function.




This is the output from that program

----------Testing with TextWire--------------------

Text Wire representation of serialised function
toUpperCase: !StringFunctions TO_UPPER_CASE

hello world -> HELLO WORLD

----------Testing with BinaryWire--------------------

Binary Wire representation of serialised function
00000000 CB 74 6F 55 70 70 65 72  43 61 73 65 B6 0F 53 74 ·toUpper Case··St
00000010 72 69 6E 67 46 75 6E 63  74 69 6F 6E 73 ED 54 4F ringFunc tions·TO
00000020 5F 55 50 50 45 52 5F 43  41 53 45                _UPPER_C ASE     


hello world -> HELLO WORLD

Straight away we see the difference between the verbosity of the self describing lambda to the predefined enum.

Enum ->  toUpperCase: !StringFunctions TO_UPPER_CASE
Lambda -> toUpperCase: !SerializedLambda {
  cc: !type net.openhft.engine.chronicle.demo.WireDemoLambdas,
  fic: net/openhft/chronicle/core/util/SerializableFunction,
  fimn: apply,
  fims: (Ljava/lang/Object;)Ljava/lang/Object;,
  imk: 5,
  ic: java/lang/String,
  imn: toUpperCase,
  ims: ()Ljava/lang/String;,
  imt: (Ljava/lang/String;)Ljava/lang/String;,
  ca: [
  ]
}

Since all the enums have been pre-defined there are no nasty surprises as to what code is going to be run.

On the downside of course you lose the flexibility of ad hoc lambdas.

Note the line:

ClassAliasPool.CLASS_ALIASES.addAlias(StringFunctions.class, "StringFunctions");

As explained earlier this means that "StringFunctions" is mapped to the class StringFunctions:

Without this line (without class aliasing) the output:

toUpperCase: !StringFunctions TO_UPPER_CASE

would be

toUpperCase: !net.openhft.engine.chronicle.demo.WireDemoEnums$StringFunctions TO_UPPER_CASE

Apart from being more verbose and harder to read it would also be a problem when passing the message between languages which is one of the reasons to use Chronicle-Wire serialisation.

Summary

Use enums when you know you need maximum efficiency and/or control. Use lambdas when you need flexibility. In practice you will probably mix both for different parts of your application.

No comments:

Post a Comment