Rider, Elephants and The Path – Script the Critical Moves

•July 7, 2014 • Leave a Comment

I’ve finally finished Switch – How to change things when change is hard.  Well worth the read.  There are already quite a few articles on the book calling out the key points/sections.  However, to remain consistent, I’ll provide my view of the key points, although Lean Thinker does a pretty good job :)

  • Page 16, “If you want people to change, you must provide crystal-clear direction” – ambiguity is never good, and leads to frustration on both sides
  • Page 17, Three-part framework: Direct the rider, Motivate the Elephant, Shape the Path
  • Page 18, Find the bright spot, and can we do more of it?  Sensible advice.  Retrospectives can benefit from this view.  To often retrospective spend more time high lightening the failures
  • Page 19, Ambiguity is exhausting for the rider, elephants will take the default path.  Its not enough for leaders to set the high level direction.  Change requires details
  • Page 62, Remove ambiguity from the vision
  • Page 81, Rider get lost in analysis. Provide a compelling destination.
  • Page 82, SMART goals – Specific, Measurable, Actionable, Relevant, Timely – remove the ambiguity
  • Page 106, See-Feel-Change philosophy
  • Page 120, Burning platform metaphor.  Negative emotions can be powerful
  • Page 126, Motivation through framing via a shorter path.
  • Page 142/144, Shrink the change – or as I often call it, create baby steps that are achievable, and when added together achieve the larger goal
  • Page 168, Act like a coach
  • Page 174, Clear the bar – growth-mindset.  No “Never”, only a “Not yet”
  • Page 212, habit are behavioural autopilot. Stand up meetings :)
  • Page 220, the check-list :) Use them to avoid the blind spots in complex environments :)

Directly Responsible Individual

•June 28, 2014 • Leave a Comment

How To Run Your Meetings Like Apple and Google offers some sensible advice.  Agenda are so often missing from so many meetings :( DRI and actions – agree.  Bring solutions :)

Camel in a Vertx Web Application

•June 26, 2014 • Leave a Comment

Mixing interesting technologies to PoC possible solution is always interesting.  I’ve become a lot more interested in Apache Camel over the last time period.  When I realised there was a Camel Vertx component, and had time to open IntelliJ, I thought I’d build a quick and dirty application to look at using Apache Camel from an up-stream integration workflow, and websocket’s for down-stream browsers.  Although the code is ropy, it hopefully provide some food for thought.

Easiest way to being with Vertx maybe to generate a project with Maven.

Smartjava, Pretech and hasCode provides a few interesting articles to assist:

  • Create a simpe RESTful service with vert.x 2.0, RxJava and mongoDB
  • Browser to browser communication with Vert.x, websockets and HTML5
  • Apache Camel + ActiveMQ Example
  • Spring JmsTemplate and MessageListener Example
  • Creating a Websocket Chat Application with Vert.x and Java

The code is “rushed”.  Enhancements:

  • Camel aggregating of messages – example use case could possibly be messages from a number of trade repositories that need to be seen in a users blotter
  • Camel transformation – Java and XML to JSON, as the upstream integrated systems are most likely not going to be JSON’d
  • Famo.us + Angular.js to improve the basic UI
package com.mycompany;

import org.apache.activemq.ActiveMQConnection;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.camel.CamelContext;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.jms.JmsComponent;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.commons.io.FileUtils;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.http.HttpServer;
import org.vertx.java.core.http.HttpServerRequest;
import org.vertx.java.core.http.RouteMatcher;
import org.vertx.java.core.json.JsonArray;
import org.vertx.java.core.json.JsonObject;
import org.vertx.java.platform.Verticle;

import javax.jms.*;
import java.io.File;
import java.io.IOException;

public class CamelWebVerticle extends Verticle {
    final ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("admin", "admin", ActiveMQConnection.DEFAULT_BROKER_URL);

    public void start() {
        createCamelRouting();
        createActiveMQConsumer();

        final HttpServer httpServer = vertx.createHttpServer();
        final RouteMatcher routeMatcher = createURLRouteMatcher();
        httpServer.requestHandler(routeMatcher);

        final JsonObject config = new JsonObject().putString("prefix", "/eventbus");
        final JsonArray inboundPermitted = new JsonArray();
        inboundPermitted.add(new JsonObject().putString("address", "msg.client"));

        final JsonArray outboundPermitted = new JsonArray();
        outboundPermitted.add(new JsonObject().putString("address", "msg.server"));
        outboundPermitted.add(new JsonObject().putString("address", "msg.client"));

        vertx.createSockJSServer(httpServer).bridge(config, inboundPermitted, outboundPermitted);
        setupEventBusListener();

        httpServer.listen(8888, "localhost");
        container.logger().info("Webserver started, listening on port: 8888");
        container.logger().info("Verticle started");
    }

    private void setupEventBusListener() {
        final EventBus eb = vertx.eventBus();

        // Register Handler 1
        eb.registerLocalHandler("msg.client", new Handler<Message<JsonObject>>() {
            @Override
            public void handle(Message<JsonObject> message) {
                container.logger().info("Handler 1 (Local) received: "
                        + message.body().toString());
            }

        });

        // Register Handler 2
        eb.registerHandler("msg.client", new Handler<Message<JsonObject>>() {
            @Override
            public void handle(Message<JsonObject> message) {
                container.logger().info("Handler 2 (Shared) received: "
                        + message.body().toString());
            }

        });

        eb.registerHandler("blotter.updates", new Handler<Message<String>>() {
            @Override
            public void handle(Message<String> message) {
                container.logger().info("Sent back data");
                message.reply("Data Ack!");
            }
        });

    }

    private void createActiveMQConsumer() {
        try {
            final Connection connection = connectionFactory.createConnection();
            connection.start();
            // Create a Session
            final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
            // Create the destination
            final Destination destination = session.createQueue("testMQDestination");
            // Create a MessageProducer from the Session to the Queue
            final MessageConsumer consumer = session.createConsumer(destination);
            consumer.setMessageListener(new MessageListener() {
                @Override
                public void onMessage(final javax.jms.Message message) {
                    System.out.println("Received " + message.toString());

                    if (message instanceof TextMessage) {
                        final  TextMessage textMessage = (TextMessage) message;
                        // Send update to web client via eventbus
                        vertx.eventBus().send("msg.server", new JsonObject().putString("text", "Async message via ActiveMQ from OMS confirmed order submitted"));
                    }
                }
            });
        } catch (Exception e) {
            System.out.println(e);
        }
    }

    private void createCamelRouting() {
        try {
            final CamelContext context = new DefaultCamelContext();
            context.addComponent("test-jms", JmsComponent.jmsComponentAutoAcknowledge(connectionFactory));
            context.addRoutes(new RouteBuilder() {
                public void configure() {
                    from("test-jms:queue:testMQ").to("test-jms:queue:testMQDestination");
                }
            });

            context.start();

        } catch (Exception e) {
            System.out.println(e);
        }
    }

    private RouteMatcher createURLRouteMatcher() {
        final RouteMatcher routeMatcher = new RouteMatcher();
        routeMatcher.get("/getBlotterSOW", new Handler<HttpServerRequest>() {
            public void handle(final HttpServerRequest req) {

                final JsonObject resp = new JsonObject();
                resp.putString("text", "SOW from DB of trades");

                req.response().headers().add("Content-Type", "application/json; charset=utf-8");
                req.response().end(resp.toString());

                vertx.eventBus().send("msg.server", new JsonObject().putString("text", "Blotter SOW snapshot"));
            }
        });

        routeMatcher.get("/", new Handler<HttpServerRequest>() {
            public void handle(final HttpServerRequest req) {
                final File f = new File("src/main/resources/web/index.html");
                System.out.println(f.getAbsolutePath());
                try {
                    // get the data from the filesystem and output to response
                    String data = FileUtils.readFileToString(f);
                    req.response().setStatusCode(200);
                    req.response().putHeader("Content-Length", Integer.toString(data.length()));
                    req.response().write(data);
                    req.response().end();
                } catch (IOException e) {
                    // assume file not found, so send 404
                    req.response().setStatusCode(404);
                    req.response().end();
                }
            }
        });

        routeMatcher.get("/submitOrder", new Handler<HttpServerRequest>() {
            public void handle(final HttpServerRequest req) {
                try {
                    // Create a Connection
                    final Connection connection = connectionFactory.createConnection();
                    connection.start();
                    // Create a Session
                    final Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
                    // Create the destination
                    final Destination destination = session.createQueue("testMQ");
                    // Create a MessageProducer from the Session to the Queue
                    final MessageProducer producer = session.createProducer(destination);
                    producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
                    // Create a messages
                    final TextMessage message = session.createTextMessage("New Order from a client");
                    producer.send(message);
                    session.close();
                    connection.close();
                    System.out.println("Message sent to ActiveMQ");

                    final JsonObject resp = new JsonObject();
                    resp.putString("text", "order sent");
                    req.response().headers().add("Content-Type", "application/json; charset=utf-8");
                    req.response().end(resp.toString());
                } catch (Exception e) {
                    System.out.println(e);
                    e.printStackTrace();
                }
            }
        });

        routeMatcher.noMatch(new Handler<HttpServerRequest>() {
            public void handle(final HttpServerRequest req) {
                final File f = new File("src/main/resources/web/"+req.path());
                System.out.println(f.getAbsolutePath());
                try {
                    // get the data from the filesystem and output to response
                    String data = FileUtils.readFileToString(f);
                    req.response().setStatusCode(200);
                    req.response().putHeader("Content-Length",Integer.toString(data.length()));
                    req.response().write(data);
                    req.response().end();
                } catch (IOException e) {
                    // assume file not found, so send 404
                    req.response().setStatusCode(404);
                    req.response().end();
                }
            }
        });

        return routeMatcher;
    }
}
<html>
<head>
    <title>ActiveMQ Vertx Camel Web PoC</title>
</head>

<body>

<form onsubmit="return false;">
    <input type="button" id="connectButton" value="Open connection"/>
</form>

<div id="submitForm">
    <form onsubmit="return false;">
        Message:<input type="text" id="sendMessage" value="Equity Order"/>
        <input type="radio" name="submissionType" value="publish"> Publish
        <input type="radio" name="submissionType" value="send" checked> Send
        <input type="button" id="submitButton" value="Submit"/>
        <br>
        <br>
        <input type="button" id="submitOrder" value="Order"/>
        <input type="button" id="getBlotterSOW" value="Blotter SOW"/>
    </form>
</div>

<br>
<br>
<br>

Messages received on browser handler:<br>
<hr>
<div id="received" class="innerbox" style="width: 400px; height: 275px;">
</div>

<script src="js/jquery-1.7.1.min.js"></script>
<script src="js/sockjs-0.2.1.min.js"></script>
<script src="js/vertxbus.js"></script>
<script>
		var eb = null;
		var addressName = 'msg.server';
		var addressClientName = 'msg.client';

		function submitMessage(type, address,  message) {
			if (eb) {
				var json = {text: message};
				if (type == 'send') {
					eb.send(addressClientName, {text: 'Send message: ' + message});
				} else {
					eb.publish(addressClientName, {text: 'Publish message: ' + message});
				}
			}
		}

		function browserHandler(msg, replyTo) {
			$('#received').append(msg.text + "<br>");
		}

		function subscribe(address) {
			if (eb) {
				eb.registerHandler(address, browserHandler);
				$('#subscribed').append($("<code>").text("Address:" + address));
				$('#subscribed').append($("</code><br>"));
			}
		}

		function unsubscribe(address) {
			if (eb) {
				eb.unregisterHandler(address, browserHandler);
			}
		}

	  	function closeConn() {
			if (eb) {
				eb.close();
			}
			$('#connectButton').val("Open Connection");
			$("#connectButton").on('click.openConnection', function() {
				openConn();
			});
		}

	  	function openConn() {
	  	  	eb = new vertx.EventBus("http://localhost:8888/eventbus");

	  	  	eb.onclose = function() {
				eb = null;
				$('#submitForm').hide();
			};

			eb.onopen = function() {
				$('#connectButton').val('Close Connection');
				$("#connectButton").off('click.openConnection');
				$('#connectButton').on('click.closeConnection', function() {
					$("#connectButton").off('click.closeConnection');
					closeConn();
				});

				$('#submitForm').show();
                subscribe(addressName);

			};
		}

		$(document).ready(function() {
			$('#submitForm').hide();

			$("#submitButton").click(function() {
				submitMessage($("input[@name=submissionType]:checked").val(), addressName, $("#sendMessage").val());
			});

			$("#submitOrder").click(function() {
                $.ajax({
                        type: "GET",
                        url: "submitOrder",
                        data: "",
                        success: function(data) {
                            browserHandler(data);
                        }
                    });
		    });

			$("#getBlotterSOW").click(function() {
                $.ajax({
                        type: "GET",
                        url: "getBlotterSOW",
                        data: "",
                        success: function(data) {
                            browserHandler(data);
                        }
                    });
		    });

            closeConn();
        });
	</script>

</body>
</html>

Wolfram Programming Cloud – Thoughts

•June 24, 2014 • 1 Comment

Wolfram Programming Cloud is now live.  There is a cloud gallery providing a range of simple samples which is a good start.  There is quite a lot of documentation which is a good start.  Having not spend enough time on Wolfram or read enough documentation, I’m wondering how best to leverage its feature set in both the buy and sell space:

  • Universal Database Connectivity (UDC) – assume I can leverage this to connect to a risk/position data repository?
  • Import and Export function are provide which is helpful
  • Given the plot histories of stock prices sample, I’m guessing by leveraging UDC, one can plot the same by using the UDC data source
  • Can I connect my InfluxDB to Wolfram given InfluxDB has time series data, and Wolfram has TimeSeries functionality?

Collateral Management Resourcing with Datomic – Part 5

•June 18, 2014 • Leave a Comment

After the interesting experience with Spring Data :( I decide to have a quick look at Datomic, even though the original plan was to use Neo4j from a repository perspective.  Datomic has for all intents and purposes a bi-temporal view of the world as I’ve previous blogged.  For the exercise at hand, I went with the free download, since this is very much a proof of concept (PoC).

Initial schema:


[
;; Team schema

{:db/id #db/id[:db.part/db]
:db/ident :project/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/fulltext true
:db/doc "A project name"
:db.install/_attribute :db.part/db}

{:db/id #db/id[:db.part/db]
:db/ident :team/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/fulltext true
:db/doc "A team name"
:db.install/_attribute :db.part/db}

{:db/id #db/id[:db.part/db]
:db/ident :person/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/fulltext true
:db/doc "A person name"
:db.install/_attribute :db.part/db}

{:db/id #db/id[:db.part/db]
:db/ident :team/people
:db/valueType :db.type/ref
:db/cardinality :db.cardinality/many
:db/fulltext true
:db/doc "A teams ref to people"
:db.install/_attribute :db.part/db}
]

Some test data


[
{:db/id #db/id[:db.part/user -1], :person/name "Bob"}
{:db/id #db/id[:db.part/user -2], :person/name "Fred"}
{:db/id #db/id[:db.part/user -3], :person/name "John"}
{:db/id #db/id[:db.part/user -4], :project/name "Risk Project 1", :team/name "Risk Feature Team 1", :team/people #db/id[:db.part/user -1 :db.part/user -2]}
{:db/id #db/id[:db.part/user -5], :project/name "Risk Project 2", :team/name "Risk Feature Team 11", :team/people #db/id[:db.part/user -3]}
{:db/id #db/id[:db.part/user -6], :project/name "Risk Project 3", :team/name "Risk Feature Team 111"}
]

Sample code, partly borrowed from the Datomic sample:


package datonicplay;

import datomic.*;

import java.io.FileReader;
import java.io.Reader;
import java.util.Collection;
import java.util.List;
import java.util.Scanner;


public class dplay {
    public static void main(String[] args) {
        final dplay teamPlay = new dplay();
        teamPlay.run();
    }

    private void run() {
        try {
            System.out.println("Creating and connecting to database...");

            final String uri = "datomic:mem://teams";
            Peer.createDatabase(uri);
            final Connection conn = Peer.connect(uri);

            System.out.println("Parsing schema edn file and running transaction...");

            final Reader schema_rdr = new FileReader("src/main/java/data/teams-schema.edn");
            final List schema_tx = (List) Util.readAll(schema_rdr).get(0);
            Object txResult = conn.transact(schema_tx).get();
            System.out.println(txResult);

            System.out.println("Parsing seed data edn file and running transaction...");

            final Reader data_rdr = new FileReader("src/main/java/data/team-testdata.edn");
            final List data_tx = (List) Util.readAll(data_rdr).get(0);
            data_rdr.close();
            txResult = conn.transact(data_tx).get();

            System.out.println("Finding all teams, counting results...");

            Collection results = Peer.q("[:find ?match :where [?match :person/name]]", conn.db());
            System.out.println(String.format("Number of people: %d", results.size()));

            final Database db = conn.db();
            for (Object result : results) {
                final Entity entity = db.entity(((List) result).get(0));
                System.out.println(String.format("%s %s", entity.toString(), entity.get(":person/name")));
            }

            results = Peer.q("[:find ?match :where [?match :project/name \"Risk Project 1\"]]", conn.db());
            System.out.println();
            System.out.println(String.format("Number of teams in \"Risk Project 1\":%d", results.size()));

            for (Object result : results) {
                final Entity entity = db.entity(((List) result).get(0));
                System.out.println(String.format("%s %s %s", entity.toString(), entity.get(":team/name"), entity.get("team/people")));
            }

            results = Peer.q("[:find ?read :where [?read team/people] [?match :project/name \"Risk Project 1\"]]", conn.db());
            System.out.println();
            System.out.println(String.format("Number of people in \"Risk Project 1\" teams:%d",results.size()));

            for (Object result : results) {
                final Entity entity = db.entity(((List) result).get(0));
                System.out.println(String.format("%s %s", entity.toString(), entity.get(":team/people")));
            }

            results = Peer.q("[:find ?read :where [?read :team/name \"Risk Feature Team 1\"]]", conn.db());
            System.out.println();
            final Entity team = db.entity(((List) results.iterator().next()).get(0));


            results = Peer.q("[:find ?c_name ?r_name :where " +
                            "[?c :team/name ?c_name]" +
                            "[?c :project/name \"Risk Project 1\"]" +
                            "[?r :team/people ?r_name]]",
                    conn.db());
            for (Object result : results) System.out.println(result);

            Peer.shutdown(true);

        }
        catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }

    }

    private static final Scanner scanner = new Scanner(System.in);

    private static void pause() {
        if (System.getProperty("NOPAUSE") == null) {
            System.out.println("\nPress enter to continue...");
            scanner.nextLine();
        }
    }
}


Clearly a work in progress. The last two queries are not correct at all. I appear to have failed to provide a sensible query for providing a list of everyone in the “Risk Feature Team 1″ team

Skill Cloud – Part 4 – Spring Data and Neo4j

•June 13, 2014 • Leave a Comment

Spent the last flight banging my head against Spring Data and Neo4j.  I was hoping Spring Data would save me some code writing, but the pain of Maven dependency hell, coupled with exceptions that are meaningless in many way, has almost driven the will to live out of me.  At the moment I’m in the world of:

Exception in thread “main” org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘entityFetchHandler’ defined in class path resource [org/springframework/data/neo4j/aspects/config/Neo4jAspectConfiguration.class]: Instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanDefinitionStoreException: Factory method [public org.springframework.data.neo4j.support.mapping.Neo4jEntityFetchHandler org.springframework.data.neo4j.config.Neo4jConfiguration.entityFetchHandler() throws java.lang.Exception] threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘nodeStateTransmitter’ defined in class path resource [org/springframework/data/neo4j/aspects/config/Neo4jAspectConfiguration.class]: Initialization of bean failed; nested exception is java.lang.reflect.MalformedParameterizedTypeException

Which even after a number of google’s is still not resolved.  I did walk into LockException compatibility issues with Neo4j 2.1.1, and then the Spring Data 3.0.1 issue :(  Here’s my pom.xml, which could be the root of the issue, who knows:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>teams</groupId>
<artifactId>teams</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<neo4j.version>2.0.0</neo4j.version>
<spring.data.version>3.0.1.RELEASE</spring.data.version>
<aspect.version>1.7.4</aspect.version>
<java.version>1.8</java.version>
</properties>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.1.1.RELEASE</version>
</parent>

<dependencies>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
<version>${spring.data.version}</version>
</dependency>

<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-aspects</artifactId>
<version>${spring.data.version}</version>
</dependency>

<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j</artifactId>
<version>${neo4j.version}</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-kernel</artifactId>
<version>${neo4j.version}</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-community</artifactId>
<version>${neo4j.version}</version>
</dependency>
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-advanced</artifactId>
<version>${neo4j.version}</version>
</dependency>

<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspect.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.4</version>
<dependencies>
<!-- NB: You must use Maven 2.0.9 or above or these are ignored (see MNG-2972) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>${aspect.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>${aspect.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<outxml>true</outxml>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
<aspectLibrary>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j-aspects</artifactId> </aspectLibrary>
</aspectLibraries>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>

</build>
</project>

Clojure: Om and Datomic

•June 5, 2014 • 2 Comments

Since Om‘s release there have been a number of interesting GitHub projects, and postings. Below is a list of possibly relevant reading for anyone interested in Om.  It’s probably also working looking at Datomic ;)

 

Given my previous posting on Clojure, I wonder how far any of the sell-side are willing to go today with Om?

 
Follow

Get every new post delivered to your Inbox.

Join 640 other followers