Category Archives: Blog

AWS CDK Custom Resources: Parsing Synth-time JSON

In a previous blog post I covered a basic overview of Custom Resources in AWS Cloud Development Kit (CDK) and in keeping with the same theme, I ran into a scenario which I thought would be more straightforward than it actually was: parsing a response containing a JSON object from a Lambda backed Custom Resource.

The problematic context was using a Custom Resource to fetch a secret from Parameter Store which was a JSON object. This object contained some setting and configuration details for AWS resources being deployed in a CDK Stack.  I thought I could simply create a Lambda backed Custom Resource which fetched the secret and returned it, so I could use it in the Stack and pass it to the resources which required that information. It turned out that it  was not so simple.

The Problem:

When you deploy your infrastructure with CDK and need to reference data stored in AWS Parameter Store, for instance, you cannot get direct access to the data in raw form at runtime. This is called “synthesis time” in CDK speak and it means that when you run a deployment, CDK synthesizes an AWS CoudFormation template (a YAML file) for the desired state of your infra with placeholder tokens for pieces of data which are not resolved until later in the physical resource creation process.

The two hurdles to get over were, as mentioned:

  • The value retrieved at synthesis time during deployment was an unresolved token, not the actual secret value.
  • The configuration stored in Parameter Store was not a simple string. It was in the form of a JSON object, and I needed to dig into the object to access specific properties off of it.

This meant that I could not leverage code constructs such as built-in parsing APIs for JSON or simple dot notation for property access with JavaScript, or easily work with in-memory data in an object type form as you normally would when writing application code.

The Solution:

CDK comes with AWS CloudFormation’s Intrinsic Functions, which you can use to work with and parse these unresolved placeholder tokens at synthesis time as if they were the resolved actual values at resource creation time.

In particular, the functions to use are cdk.Fn.split and cdk.Fn.select, which can be used to split the JSON string and select the value. This may not be the most elegant or robust way to handle parsing unresolved objects at synthesis time, but it worked, is practical and saved me further lost time exploring other solutions which just didn’t wind up working out (such as building a custom Lambda to use with a non pre-baked standard Custom Resource which parses and returns the object).

I also used the built-in Custom Resource AwsCustomConstruct constructs provided by CDK to fetch the parameter from Parameter Store.  What’s great about these built-in Custom Resources, is that you don’t have to create a Lambda, manage dependencies, or write any custom logic to accomplish common use case tasks. The pre-built Custom Resource is automatically backed by a Lambda or functionality to accomplish a given action (in this case, ‘getParameter’ to retrieve a parameter from AWS Parameter Store).  This saves you extra overhead and reduces moving parts in your IaC code.

In case it is helpful to others running into the problem, the code for the approach is below (using this example from the AWS CDK documentation as a reference):

As an example, the kind of data I was storing and retrieving from Parameter Store was something like:

{ "prop1": "prop1Value",  "prop2": "prop2value" }
// cdkProject/lib/myStack.ts

import * as cr from "aws-cdk-lib/custom-resources";

const JSON_PARAM_NAME = "/myapp/secrets/jsonsecret";

// Using the built-in AwsCustomResource construct with the action "getParameter":
    const getParameter = new cr.AwsCustomResource(this, "GetParameter", {
      onUpdate: {
        service: "SSM",
        action: "getParameter",
        parameters: {
          Name: JSON_PARAM_NAME,
          WithDecryption: true,  // Needed for SecureString parameters
        },
        physicalResourceId: cr.PhysicalResourceId.of(Date.now().toString()), // Forces fetch to parameter store on each deploy
      },
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
        resources: [
    `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter${JSON_PARAM_NAME}`,
        ],
      }),
    });
// Parameter Store values are by default available on the Parameter.Value property of the response from SSM:
const paramValue = getParameter.getResponseField("Parameter.Value");

// Here we need to use intrinsic functions to dig into the future resolved actual value of the retrieved secret (which is a placeholder token at synth time):
    const prop1Split = cdk.Fn.split('"prop1"', paramValue);
    const afterKeyPart = cdk.Fn.select(1, prop1Split);  // Part after "prop1" key
    const afterColon = cdk.Fn.split(':', afterKeyPart);
    // the value part after the colon
    const valuePart = cdk.Fn.select(1, afterColon);
    const valueSplit = cdk.Fn.split('"', valuePart);
    const prop1Value = cdk.Fn.select(1, valueSplit); // First split part is the value for the property

    new cdk.CfnOutput(this, "TheParamValue", {
      value: prop1Value,
      description: "Parsed value from the custom resource",
    });

After implementing the use of Instrinsic Functions and leveraging the built-in Custom Resource Action CDK provides for fetching data from AWS Parameter Store, I was able to successfully retrieve and insert the important piece of configuration needed for my Resources.

If there are more robust ways to handle JSON object like pieces of data in a context like this, please feel free to leave suggestions in the comments, but this turned out to be a working solution that enabled me to move forward and deploy my properly configured infrastructure.

Understanding Custom Resources in AWS CDK

A Brief Introduction: AWS CDK

AWS Cloud Development Kit (CDK) is an open source Infrastructure as Code tool recommended and maintained by AWS to manage cloud infrastructure via code. It offers management using languages such as JavaScript, TypeScript, Python, C#, GoLang and Java which means you can take advantage of code constructs such as loops, variables and conditionals to orchestrate how your infrastructure such as EC2 instances, S3 Buckets, RDS instances and other AWS resources are managed, updated and deployed.

What is Infrastructure as Code?

IaC (Infra as Code) is an approach to managing resources needed to host and run your application or website using actual written code instead of, for example, manually ssh’ing into your web servers or logging into your Cloud console UI to turn buttons and knobs or do administration manually. It’s a modern, robust and more automated way to reduce user errors when doing this kind of manual maintenance or updating of your software infrastructure.

Traditionally, IaC management in AWS was done directly with AWS CloudFormation, which leverages YAML files and syntax. While this was a viable option, it could also be quite verbose, complicated and overwhelming with very large YAML files and a large array of properties and settings to memorize or look up.

AWS CDK actually still uses CloudFormation under the hood, but it abstracts the details and complexity away allowing you to use code in a variety of languages. The library comes with helper functions that encapsulate things like adding Allow IAM permissions to resources in one line of code, instead of having to construct a more complex and verbose YAML block, for instance.

Custom Resources

This brings us to the topic of this post which is a construct in AWS CDK called a Custom Resource. A Custom Resource is an entity in AWS CloudFormation which could be used to extend “out of the box” functionality available conventionally.  For example, if you needed to run some logic or encapsulate an infrastructure operation that didn’t come “out of the box” with a Resource in CloudFormation, you could use a Custom Resource to combine available Resources or run custom logic as part of your deployment process.

What is a Resource?

Here, we’re talking about logical resources, which means a resource that represents a physical one – for example, an AWS::EC2::Instance resource in CloudFormation is a representation of an actual EC2 instance that would be deployed into your AWS account’s VPC.  AWS CDK uses an abstraction called a Construct, which can represent a CloudFormation Logical Resource, but is a simpler object to deal with, coming with member functions and built in helpers to accomplish common infra management tasks that normally would be more cumbersome to implement in raw CloudFormation YAML templates.

With the background and terminology out of the way, now let’s understand why and when to use Custom Resources in CDK.

Let’s say you want to update a list of email addresses for a subscription to an AWS SNS topic, so that when you run a deployment, a list of emails is checked against to determine what set of users receive notifications or emails from the SNS topic when a message is published.

There is not a built in Resource to accomplish this, so you can make a Custom Resource backed by a AWS Lambda (a small serverless function) which could reach out to a data store (AWS Parameter Store, for instance), retrieve a list of emails and create a subscription in SNS for the emails when you release or update your application infrastructure. This would automate manual maintenance or updates to the SNS subscriptions where you’d otherwise have to login to AWS console, go through the steps in the SNS topics UI and start typing in email addresses and creating subscriptions manually.

A Concrete Example:

Implementing the above scenario, this could be the Lambda which you can define in your project (and commit to a repo for version tracking with git). It uses the AWS SNS client library and the SSM client library to communicate with Parameter Store to retrieve a list of stored emails and create an SNS subscription to a topic for them. This example will focus on the code to define and add the Custom Resource with the Lambda and assumes the other constructs such as your SNS topic have already been defined and created.

In the Lambda, note that you need to send a pre-determined and required response structure from the Lambda to let CloudFormation/CDK know that the Lambda completed (either successfully or otherwise). This is what the sendResponse  function does. Without it, CloudFormation/CDK will not know that the Lambda is done doing what it needs to do and will time out and Fail the deployment.

// someLambdaFolder/index.js
const { SNSClient, SubscribeCommand } = require("@aws-sdk/client-sns");
const { SSMClient, GetParameterCommand } = require("@aws-sdk/client-ssm");
const https = require("https");
const snsClient = new SNSClient({ region: process.env.AWS_REGION });
const ssmClient = new SSMClient({ region: process.env.AWS_REGION });

function sendResponse(
  event,
  context,
  responseStatus,
  responseData,
  physicalResourceId
) {

  return new Promise((resolve, reject) => {
    const responseBody = JSON.stringify({
      Status: responseStatus,
      Reason: `See CloudWatch Log Stream: ${context.logStreamName}`,
      PhysicalResourceId: physicalResourceId || context.logStreamName,
      StackId: event.StackId,
      RequestId: event.RequestId,
      LogicalResourceId: event.LogicalResourceId,
      Data: responseData,
    });

    const responseUrl = new URL(event.ResponseURL);

    const options = {
      hostname: responseUrl.hostname,
      port: 443,
      path: responseUrl.pathname + responseUrl.search,
      method: "PUT",
      headers: {
        "content-type": "",
        "content-length": Buffer.byteLength(responseBody),
      },
    };

    const req = https.request(options, (res) => {
      console.log("Status code:", res.statusCode);
      resolve();

    });

    req.on("error", (error) => {
      console.error("sendResponse Error:", error);
      reject(error);
    });

    req.write(responseBody);
    req.end();
  });

}

exports.handler = async (event, context) => {
  console.log("Event:", JSON.stringify(event));

  try {
    if (event.RequestType === "Delete") {
      // No action needed on delete
      await sendResponse(event, context, "SUCCESS", {}, PhysicalResourceId);

      return;
    }

    // Passed in via properties on the custom resource

    const { EmailsParamName, TopicArn, PhysicalResourceId } = event.ResourceProperties;
    const ssmResult = await ssmClient.send(
      new GetParameterCommand({ Name: EmailsParamName })
    );

    const emailsRaw = ssmResult.Parameter.Value;

    const emails = emailsRaw
      .split(",")
      .map((e) => e.trim())
      .filter((e) => e);

    console.log("Parsed emails:", emails);

    for (const email of emails) {
      await snsClient.send(
        new SubscribeCommand({
          Protocol: "email",
          TopicArn: TopicArn,
          Endpoint: email,
          ReturnSubscriptionArn: true,
        })
      );

      console.log(`Subscribed ${email} to ${TopicArn}`);
    }
    await sendResponse(event, context, "SUCCESS", {}, PhysicalResourceId);
  } catch (error) {
    console.error(error);
    await sendResponse(event, context, "FAILED", { Error: error.message }, PhysicalResourceId);
  }
};
// someLambdaFolder/package.json

{
  "name": "lambda",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@aws-sdk/client-sns": "^3.810.0",
    "@aws-sdk/client-ssm": "^3.810.0"
  }
}

Now for the CDK implementation, you’d create a Lambda resource, and add permissions required to accomplish the task of communicating with Parameter Store (SSM) and the AWS SNS service:

// projectRoot/lib/stacks/someStack.ts

import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; // Use NodejsFuction construct to get auto-installed npm dependencies
import * as cr from "aws-cdk-lib/custom-resources";
  const subscriptionHandler = new NodejsFunction(
    this,
    "SnsSubscriptionHandlerLambda",
    {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: "handler", // Name of the exported function in index.js
      entry: path.join(lambdaPath, "someLambdaFolder", "index.js"), // point to where you define the lambda and logic in your project
      timeout: cdk.Duration.seconds(300),
      bundling: {
        externalModules: [], // Specifying this as an empty array includes all packages listed in the lambda folder's package.json and installs them when deployed. include a package-lock.json in the lambda folder if you want consistent versions
        minify: true, // Minify the code to reduce bundle size
      },
    }
  );
    // Grant Lambda permission to read SSM param & manage SNS subscriptions
    subscriptionHandler.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ["ssm:GetParameter", "sns:Subscribe"],
        resources: [ `arn:aws:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter${cwEmailsParamName}`,
          alarmTopic.topicArn,
        ],
      })
    );

    // Grant CloudFormation permission to invoke the Lambda
    subscriptionHandler.addPermission("AllowCloudFormationInvoke", {
      principal: new iam.ServicePrincipal("cloudformation.amazonaws.com"),
      action: "lambda:InvokeFunction",
      sourceArn: cdk.Stack.of(this).stackId, // Get stackId from the parent stack
    });

Note, that you should generally use the NodejsFunction construct from `aws-cdk-lib/aws-lambda-nodejs` in CDK, which provides options to automatically bundle and install npm dependencies for you. Otherwise, you’d have to npm install them manually before deploying if using another Lambda construct. You would create a package.json file in the same folder as your Lambda definition file which CDK would pick up automatically and the dependencies defined in that file would be installed when deploying the Custom Resource Lambda.

Finally, you need two further elements to complete the Lambda backed Custom Resource implementation: a Provider, and a Custom Resource construct.

const provider = new cr.Provider(this, "SnsSubscriptionProvider", {
      onEventHandler: subscriptionHandler,
    });

    new cdk.CustomResource(this, "SnsSubscriptionResource", {
      serviceToken: provider.serviceToken,      
    // This is what is passed to the Lambda on invocation as arguments:
    properties: {
        EmailsParamName: cwEmailsParamName,
        TopicArn: alarmTopic.topicArn,
        // Ensures that the function is invoked on every deployment:
        PhysicalResourceId: cr.PhysicalResourceId.of(Date.now().toString())
      },
    });

The Provider construct is a wrapper that encapsulates and extends the Lambda resource construct you created so that you can use it as a backing for the Custom Resource (for example, it exposes a service token which is a unique Identifier/ARN for the Lambda which must be provided to identify which Lambda resource is to be invoked).

A key thing to remember is that the Lambda/Custom Resource is not invoked if none of the properties change, even if you update the code in the Lambda index.js (index.ts) file!  For that reason, adding a dynamically changing unique value such as Date.now().toString() or some kind of “Version” property which increments monotonically will ensure a re-invocation of the Lambda on every deployment if you need to act on fresh and changing data.

That’s Custom Resources in a nutshell. I thought it would be a good topic to cover and try to boil down into a brief post in case it helps others get a quick distilled overview of them and when they are useful.

Complexity In a Nutshell

In this In A Nutshell series, I’m going to be covering Complexity, inspired by recently reading a fantastic book by Sean McClure titled, Discovered, Not Designed (Building Things in the Age of Complexity).  The Complexity being discussed here is not the colloquial kind, but is referring to the modern definition which describes things that are more than the sum of their parts.  This in some ways dovetails off of my last post about Chaos Theory, which is a related topic. My goal is to briefly touch on the main ideas of modern Complexity along with how what I learned from reading McClure’s book lends insights into building and managing complex systems. I’ll also look at how some of those ideas can be related to the practice of software engineering.

What is Complexity?

For the purpose of this post, Complexity is meant to describe systems whose behavior cannot be explained by a causal chain of events or actions (or, as McClure puts it in English-styled humor, “things bumping into other things”).  A car engine is an example of a deterministic system, i.e. a non-complex system that is predictable, where the parts not only make up the whole, but can be causally linked to explain why the engine runs and how it works.

In the modern age, as things progress more and more, we are starting to have to deal with and build truly complex systems such as A.I. which are not reductionist in the classic Newtonian sense.  What this means is that you can’t understand the system by understanding the individual components as you can with a car engine.  What the system is, is not the sum of its parts, but all of the inter-weaving interactions of its parts that in combination create an emergent outcome, structure and behavior which could be fundamentally different from any of its parts.  This makes understanding such a system in terms of causal reasoning unachievable, as if it were a “black box” which we cannot look inside to understand, making it impossible to reverse engineer or analyze in the same way we would for deterministic systems.

This suggests a need to consider changing one’s paradigm for approaching how to deal with truly complex systems. Not only how to work with them, but how to build them.

Key Terms:

  • Emergence: An unexpected behavior, attribute or structure that materializes in or from a complex system. The cause cannot be reverse engineered or determined, only witnessed and verified after the fact. This is a key property of Complex systems.
  • Self-Organization: An interacting of parts in a system in an organized or seemingly intelligent way that is not designed, predictable or planned. A colony of ants self-organizing to accomplish a task is a well known example of this in nature. It’s also worth noting that the individuals in the group only have local knowledge and not that of the group or a high level view of “how they fit in the whole” to guide or direct their behavior.
  • Nonlinearity: A result or outcome that is not arrived at “in a straight line”, or in steady, consistent steps (i.e. repeatedly adding constants to something). Nonlinear results are the outcome of sudden jumps and can be surprising or hard to predict intuitively. For example, an exponential equation is nonlinear because as you exponentiate a higher and higher number, the result increases in larger and larger jumps. Complex systems are nonlinear in that they produce structures and behavior that are more than the “steady” addition of the parts of the system.
  • Multiple Realizability: Outcomes or structures can be produced in many different ways via multiple paths or configurations. An argument could be made that the structures we see in nature are those with the most number of possible configurations to arrive at. In deterministic systems there is a right path to get the right output, where in Complex systems there is no “right” path, only a right outcome, and there are multiple potential ways to get there.
  • Meta or Hyper-parameters: Going outside of a system and looking back into it. For example, “meta-data” is data about data, Hyper-parameters are parameters that affect internal parameters in a Neural Network.

The Primary Properties of Complex Systems

An important thing to understand about complex systems is that they cannot be understood in the same manner that deterministic systems are by using causally based reasoning. We know we are dealing with a complex system if it has certain properties that can only be observed and emerge after the fact. These properties include Self-Organization, Emergence, Group Selection, Nonlinearity and Multiple Realizability.

A key idea is that complex systems are not additive. The end result or behavior is not something you can get by adding one thing onto or with another in succession or in a specifically planned way. The structure or behavior of a complex system is the aggregate, unpredictable cumulative result and emergent properties that arise from all the overlapping interactions of its parts.

Group Selection

One of the most important points made in “Discovered, Not Designed” was that Nature uses the full distribution.  What this means is that groups or the whole are the important “unit” of effectiveness, not individuals. You can’t think in terms of individual parts to the system in Complexity – only the group can be considered. For example, what makes an ecosystem is not “birds” or “rabbits”, but all of the components in aggregate working together as a group, from all corners of the distribution.

This is contrary to how we like to think about how a lot of things are distributed in terms of the Bell Curve, or the Gaussian distribution, where there is a peak which represents more important information compared to the smaller tails. In nature and complex systems, the tails are just as important as the peaks, so to speak, because the system is the emergent outcome of all of the parts interacting together – the full distribution.

How Do We Deal With Complexity?

 

Design Is the Enemy

Complex systems cannot be designed in the same way non-complex systems can. In trying to build truly complex systems such as those we find in nature, where the internals are too complicated to be comprehensible or reason about, McClure suggests we must go outside of the system. We must go “Meta” in order to build truly complex things which manifest emergent outcomes and behavior that align with what we want. And we only know that we’ve built what we want when we observe those emergent properties manifesting via intuition and heuristic driven trial and error.

For example, to manipulate the behavior of A.I. models such as Neural Networks, the hyper-parameters and meta properties that are outside of the system are designed and adjusted, but not the inner workings of the system which are far too complex and numerous to manage. How could you plan out or “design” a system with the right settings for billions of parameters? You can’t. The right settings emerge via iterative self organizing processes such as Reinforcement Learning, for example using multiple LLMs recursively (“Meta-LLMs”), or via Gradient Descent, which is an iterative automatic process that “finds” the right weights and settings and can be driven by hyper-parameters such as the Learning Rate.

Heuristics and Intuition

When dealing with true Complexity, what is more effective than pre-planned design is impassioned trial and error in combination with heuristics and intuition. Heuristics are built in “short-cuts” we use for making decisions in the midst of complexity. For example, if you see a panicked crowd running in one direction, you don’t stop to analyze what they are running from. You make the heuristic decision to run with them.

Though it’s worth considering how heuristics can sometimes lead us to make the wrong decision, McClure points out that they are probably hard-wired in us for a reason. They have stood the test of time, are built on deep-ingrained instinct, and therefore should be drawn upon in the context of working with Complex systems.

It is an interesting argument for favoring the use of what scholar Daniel Kahneman, in his book Thinking Fast and Slow, calls “System 1” as opposed to “System 2” (a more methodical, analytical and slow approach to problem solving). Truly complex problems found in things such as driving in heavy traffic or difficult terrain, negotiating with other people, or spontaneously improvising in a jazz trio are not solved via a slow, rigorous and analytical manner.  They are solved “immediately” by using ingrained, time tested instincts whose purpose is to deal with such complexity in the first place.

Though it is considered more rigorous and scientific to use formal analytical methodology and Mathematical formulas or reasoning to solve problems, McClure makes the point that Mathematics may just be a residual of phenomena. Even famous scientists such as Einstein remarked at how much more intuition played a role in their problem solving and scientific discoveries.

Another example is James Clerk Maxwell, who formalized Electro-Magnetic Theory using Math, but was inspired by his precursor Michael Faraday, who was much less “rigorous” and instead used naive intuition driven experimentation and trial and error to first discover electro-magnetism.

One of my favorite phrases in the book was “fundamentals do not precede application”. Taking music theory as an example, the rules and fundamentals of voice leading which many great composers such as J.S. Bach used, were discovered, not designed, in that they were emergent properties that musicians noticed over time via trial and error as they let their ear and musical intuition lead them to recognize configurations of notes which sounded more harmonious than others.

Is Software Engineering Complex?

In my previous blog post on Chaos Theory, I speculated that software development unfolds in what could be compared in some ways to a Chaotic System. I am also convinced that it is Complex. We are dealing with teams of people (already that is complex enough) and changing requirements that emerge based on feedback from the network of customers, users and stakeholders. This is in addition to a dynamic technology space where tools, security patches and paradigms are constantly shifting. All of these parts are interacting and affecting each other in this system.

Ideally, unless we’re talking about an LLM, a Neural Network, or to a lesser extent a highly distributed asynchronous system, we want to eliminate stochastic processes or behavior in the software itself and make our software systems deterministic. The software itself should not be Complex, but software development sure as heck is.

Thoughts on how some of the ideas from McClure’s book could be related to everyday software engineering:

It already is considered and known by some to be an effective practice that stories or behavioral goals of a system should not be specified to great detail. Only the desired outcome and behavior at a higher level should be specified, and the inner details of the implementation will emerge, so to speak, as developers iterate and actually do the work.

Also, frequent quick feedback loops (communication with stakeholders and review of the software in its current/intermediate state) in an iterative fashion can allow us to continually react to emergent changes of user preference and new desires of the software’s behavior or changing customer goals as all the “parts” (i.e. people, products, environment, etc.) interact during the process of software development.

This is designing the system from the outside looking in, in that you allow a complex system to work – being a team of software developers along with changing dynamics and stakeholder preferences that interact and evolve – and react to emerging properties from those interactions to move towards a ‘meta-goal’. NOT trying to plan it all out, NOT trying to design every specific, but discovering the ideal emergence of the software and goals aligning.

Final Thoughts:

The key takeaway for me is to have humility. To realize that when we are dealing with truly complex systems, we can’t predict their behavior or control them as much as we’d like, and so must accept this and use it as a starting point.

The other key insight was the suggestion that truly complex systems should be approached “from the outside” at the Meta level. We need to let go of wanting full control of all the internal details and focus on facilitating the emergence of desired properties of the system instead.

A lot of real life and business problems are complex in this sense. While it may be somewhat useful to have a “plan”, or chart and try to control internal metrics (“Velocity”, etc.), I think that the reality is that these measurements only tell part of the story and may even be misleading in some cases.

An alternative approach is paying attention to the feedback from working with or building complex systems, or watching for evidence that signals to us that we are dealing with Complexity. Stepping outside of the system, making judgements based on properties we see emerging and using that to verify or adjust course instead of following a design or plan, may be how we need to deal with true Complexity.

Further Resources:

Chaos Theory in a Nutshell

In this In A Nutshell series, I briefly summarize and break down the main ideas in Chaos Theory, which is the study of how deterministic systems can produce unpredictable results and is also considered one of the “complex sciences”.  It’s a subject that I’ve become fascinated with in addition to fractal geometry, and thought it would be interesting to see how these ideas can be related to software engineering.

What is a Chaotic System?

Chaotic systems in a nutshell are deterministic systems that you would think are predictable, but wind up producing wildly unexpected output. The weather is an example of a chaotic system and the famous colloquial example of chaos is The Butterfly Effect, for example: a butterfly in China can flap its wings which eventually causes a hurricane in New York.

Key Terms:

  • Iteration: Doing the same thing again and again using the previous step’s output as the next step’s input. Note: Iteration is not repetition. In iteration, the output is used as the next input whereas in repetition, this does not occur.
  • Seed: the initial condition or input to a dynamical system at the start of an iterative process.
  • Orbit: Also known as an Itinerary, it is the various outputs in sequence starting from iterating from a seed.  You can plot and graph these outputs as they change over time and get insight into the behavior of various systems. Chaotic orbits become unpredictable and erratic on the plot.
  • Period: A repeated cycle or pattern in an orbit. Periods can have a length or duration, for example,  an iterated function that produces 2 values that oscillate back and forth as iteration continues has a period of 2.
  • Dynamical System: A system where some variable or set of variables changes over time. Iterated functions whose outputs change over time are one type of a dynamical system.

Chaotic systems have four main properties:

  • Sensitive Dependence on Initial Conditions: This means that if you change the starting condition of a system by a little bit, it will produce a much different outcome.
    • Think of a product line conveyor belt, for example, where you set the speed to 2.1 feet per second vs. 2 feet per second. You would think increasing the speed by 0.1 ft./s would produce a proportional increase (i.e. 5%) in output from the line.  But, imagine that after doing this you get a surprising 37% increase in output.
    • So a small change in the starting condition produced an unpredictable result compared with that of another miniscule different starting condition.
    • To further illustrate the importance that iteration plays in these dynamical systems, imagine that as each product gets to the end of the line, it is fed back into the start of the same conveyor belt system, and at the end, that resulting product fed back in at the beginning again, and this cycle is repeated (iterated).
  • Bounded: There are boundaries in the system output. The conveyor belt speed cannot be increased to infinity, for example.
  • A-periodic: There is not a constant repeated pattern so that you can predict how the system behaves long into the future.  The orbit or itinerary of the system does not contain any periods or repeated patterns going forward in time.
  • Deterministic: This means that if you plug in the same inputs to a system, you get the same outputs. So imagine that if we placed an unrefined product on the conveyor belt, we would always get the same refined product at the end (given the same unrefined product).

Behavior of Chaotic Dynamical Systems and Universality:

One of the most interesting things about Chaos Theory is that amidst the chaos in these systems there is also a strange order and universal behavior between systems. For example, something known as a bifurcation occurs as systems transition from ordered to chaotic states.

An example of this is a dripping faucet. As you slowly increase the flow speed of the stream, the drips will predictably start falling twice as fast, and then as you increase the flow more, twice as fast as that, and the rate continues to double until the rate of the drips are unpredictable and go into a chaotic state. This is measurable and found in many chaotic systems from unrelated domains as a consistent observed behavior.

Feigenbaum’s Constant:

What’s even more interesting and strange is that as these doubling periods increase, their proportional duration to each other will start to remain constant (a ratio known as Feigenbaum’s constant which is about 4.669). So the length of a doubling period will constantly wind up being 4.669 times the length of the following doubling period and this continues until the chaos state comes about.

Implications of Chaos Theory applied to Software Engineering:

Planning and Estimating, i.e. Waterfall vs. Agile:

One recurring property that exists in a lot of chaotic systems is that their trajectories (or orbits) can be predictable in the short term if compared with the trajectories of the same system with slightly different starting conditions.  But, these trajectories will diverge wildly as time progresses.  So, given this observation, the key in reliably predicting the behavior of these systems is to focus on short term timelines (consider that the weather, a chaotic system, can only be predicted reliably a relatively short time into the future).

Applying this idea to software development, this warrants caution against predicting or estimating outcomes in the distant future, considering the chaos and unpredictability that can unfold as time and development progresses.   Waterfall planning, for example, should be avoided and the pitfalls of putting ill-invested faith in these long-term estimations are well known.

Likewise, putting too much faith into estimations using a previous project’s trajectory to predict an upcoming similar project’s trajectory is also not advisable.  If the development of a software system is chaotic, as I suspect it is, then a small difference in the upcoming project compared to the previous similar project could result in wildly different trajectories.  It reinforces the importance of short iterations and frequent feedback loops to reset our trajectory constantly to look at a short time interval into the future, so that the results of our efforts are somewhat predictable.

Iteration:

As Tom DeMarco puts it in his book, Structured Analysis and System Specification (an oldie, but a goodie!), the brain operates naturally and most effectively as an iterative processor.  The fact that complex dynamical systems found all around us and in nature can be built from simple iteration is further proof its effectiveness.  One of the pieces of advice about how to build and design complex systems in the book is to simply start.  Put something down on paper, even if it’s bad, and then iterate on it over and over again to continuously improve it little by little until it takes shape into a well formed and robust design.

We shouldn’t underestimate the power of iteration.  The results of iterated chaos games and the resulting fractals from iteration like the Barnsley Fern, for example, show us the elegant and complex output that can be produced from simple iteration.

I can also see a parallel with Test Driven Development (TDD), in that a simple iterative feedback loop (Red, Green, Refactor) can produce an emergent design as the process continues.  This also mirrors at least in part what happens in chaos games, like the famous one that produces a Sierpinski Triangle; An elegant fractal emerges by iterating over simple rules.

Building Complexity from Simple Components:

More technically speaking, the idea of the recursion and iteration of deterministic functions in software and their similar counterparts and use in analyzing dynamical chaotic systems is noteworthy.  The idea that the iteration of simple functions can produce infinite complexity (see the Mandelbrot set) is food for thought and is a reminder that the best way to build complex systems elegantly should be to try to use simple building blocks and compose them.

Deterministic functions are also important in the functional programming paradigm.  Even though there are some random and stochastic elements found in the rules for Chaos games, you could still think of them as being input to a deterministic function.

An interesting thought experiment might be to ask how one might build complex programs or systems in a fractal or recursive manner.

Final Thoughts:

One of the things I find profound about the study of chaos is that these systems are observed occurring in nature and in mathematics and suggest that outcomes are not necessarily pre-determined, or at least you can’t assume outcomes or predict them in a lot of cases.  Like Keynes’ observation that true probabilities can only be calculated in theory due to inherent uncertainty, Chaos Theory gives me a scientific excuse to be pleasantly surprised and optimistic in life.


FURTHER RESOURCES:

  • Chaos and Fractals: An Elementary IntroductionThis blog post was based on concepts introduced to me in this great book on Chaos Theory and Fractals by David Feldman. Highly recommended as a good introduction to the domain of Chaos Theory and in addition some fascinating information on fractal geometry is provided as well.

  • Check out my GitHub repo with demonstrations of things like the Chaos Game implemented in Python.

Unit Testing In a Nutshell

Over the last several months I have been taking a deeper dive into software testing best practices.  Testing is a common “pain point” for a lot of developers and I’ve seen multiple projects that even have no tests at all because “it slows us down”. I’m a big believer in testing (and test driving) software to reduce bugs, enable confident refactoring and improve the design of the system.  Having no tests or poor coverage will really slow projects down in the long run. This post distills some best practices for Unit Testing and my hope is to make a case for embracing testing as a tool to help us build better software and make our lives easier, not more difficult.

Unit Testing in a Nutshell:

  • A unit test verifies a small piece of the system, runs fast and is isolated from other tests.
  • Unit tests should verify units of behavior and not just lines of code or implementation details. This makes the tests resistant to refactoring so they don’t break when implementation, but not functionality, changes in the system.
  • Unit tests can serve as documentation or a kind of Spec for the system and ideally should be written in a way that even a non-developer or business person can understand (i.e. the description of the test should describe a business scenario in plain English)
  • The purpose of unit tests are to enable sustainability of the system (to keep it from grinding to a halt due to bugs or breaking changes in the long run and to allow for refactoring and making changes with confidence)

Definitions:

  • Spec: Short for Specification. A description of what the software or system should do to satisfy stakeholders goals.
  • Unit of Behavior: A small grouping of code representing some behavior of the system. Unlike a unit of code (i.e. a single function or class), a unit of behavior can span multiple classes or objects.
  • Regression: A bug. When a feature stops working after a certain event (i.e. a code change etc.)
  • System Under Test (SUT): The part of the code in the system that is being tested. i.e. the classes, objects or functions.
  • Test Isolation: The ability for tests to run in parallel, sequentially, and in any order is called test isolation.
  • False Positive: A test that fails, but for the wrong reasons. This term can be confusing, so think of a test for the flu – if you test positive, that’s bad and means you have an illness, so here “positive” counter intuitively translates to a negative (i.e. a test failure) result.
  • False Negative: A test that passes, but for the wrong reasons. 
  • Test Double: A Mock, Stub, Spy, Dummy or Fake to mimic a dependency instead of using it directly in a test. The term comes from “Stunt Double” used in film making.  A future blog post will explore these in more detail.

What makes a good test? The properties of a good unit test:

  1. Resistant to regressions (Catches bugs.)
  2. Resistant to refactoring (The test is not brittle and will not break when code implementation, not functionality/behavior, changes.)
  3. Runs fast for rapid feedback (You should be able to keep unit tests running during development to verify changes do not break something as you’re coding.)
  4. Easy to maintain
  5. Isolated (The test can be run independently of other tests and in any sequence of order. This allows for parallel execution for faster test runs and eliminates unexpected bugs caused from shared states.)

Do not focus on achieving 100% Line Coverage, focus on coverage of Behavior and Use Cases

One of the benefits of good test coverage is to give you confidence that changes to the system (i.e. refactoring or adding features) does not break it’s desired behavior.

Focusing on achieving 100% line coverage can, counter-intuitively, actually be problematic because it forces you or your team to write tests just to achieve that benchmark, whether the tests are valuable or not. This also has the side effect of incentivizing testing implementation details making for brittle tests that will break when refactoring occurs. You will start getting more and more false positives from your tests which will reduce confidence in them.

Not only does this create extra work unnecessarily, it also makes developers hesitant to make changes and refactor because they will then need to fix a bunch of brittle tests. The code stagnates and rots over time.  Line coverage requirements are not necessarily a good idea and focusing on testing the observable behavior of the system will make for more valuable tests to cover what really matters.

Unit Tests need to run fast

You should have a fast running suite of tests that you can leave on watch mode while developing. The purpose of this is to enable you to catch bugs and breaking changes as soon as they happen. This greatly decreases debugging time because it saves you the trouble of hunting down exactly which part of the code broke something. If you have the tests running and they start failing, the part of the code that broke things was….the last change you made. This quick feedback loop will save lots of time that would otherwise be dedicated to debugging.

A Note on Naming Tests:

You should try to name your tests in such a way that a business person or domain expert would understand what the test is covering. This helps to make your test suite a documentation or spec for the system and what it’s expected functionality is. This in turn also makes the system as a whole and what it’s goals are easier to reason about and understand.

Suggestion:  Tests should verify facts about the system. You may want to consider removing words like “should” and replacing with “is”, and when naming tests in general consider making a statement as if it were a fact about the behavior of the system.  The C# testing tool XUnit, for example, actually uses [Fact] to declare a test definition.

Another suggestion is to just use plain English when naming tests instead of following a convention like: [MethodUnderTest]_[Scenario]_[ExpectedResult].
You shouldn’t use the name of the method or class in the test description because that is an implementation detail. If the function name changes, then you have to update all of the test names referencing it.

The Classical School vs. London School of Unit Testing:

There are two schools of testing: The Classical School and The London School.  There are pros and cons to each, but I prefer the Classical School style. It is advocated by industry stalwarts such as Martin Fowler and the arguments for it, in my opinion, are compelling.

Differences:
  • The London School prefers using Test Doubles for all dependencies except immutable objects vs. the Classical School which only mocks shared dependencies
  • The London School considers a unit a class or function, while The Classical School considers a unit a unit of behavior which could span multiple classes or functions.
  • The London School considers a test isolated if the class or unit is isolated completely from other classes through mocking all outside dependencies, while The Classical School considers isolation of an entire test (the unit of behavior is separate from another unit, dependencies for classes may not be mocked).

One of the main advantages of The London School of testing is that if a test fails, you know exactly where and what part of the code failed since it considers a unit to be very granular (a specific isolated class or function). The disadvantage is that more mocking makes for more fragile and brittle tests.  Since mocking everything except immutable objects, including intra-system communication, will require tests knowing more about implementation details instead of only verifying observable outcomes, they will be less resistant to refactoring.

Even though The Classical School of testing has some disadvantages like the lack of easily finding the specific failing function or class, these can be overcome by running the tests constantly to give you instant feedback when a breaking change is introduced – the source of the bug is easily found, it’s the last thing you wrote. The main advantage is that by testing observable behavior and outcomes, your tests become more resistant to refactoring, valuable and test the meaningful parts of the system.  This also provides important feedback about pieces of code that cause cascading failures across the system. The London School approach would only indicate the specific class that failed since the classes under test are completely isolated.


FURTHER READING AND RESOURCES: 

  • Unit Testing: Principles, Practices and Patterns by Vladimir Khorikov. An excellent book on best practices and approaches to Unit Testing including when and what to mock, what to test and more. This blog post was inspired by and based on ideas in this book.

BEM IN A NUTSHELL

BEM is a naming convention for CSS classes that facilitates writing clean and organized CSS.  This blog post will boil down what BEM is and how to use it in this “In a Nutshell” series entry.

BEM In a Nutshell:

  • BEM stands for Block, Element, Modifier, each of which represent the 3 parts of a class name using this convention.
  • The format for the BEM naming convention is <Block>__<Element>--<Modifier> where Block, Element and Modifier represent a part of the class name (see Definitions below).

The reason BEM is a useful naming method is because it explicitly describes which element(s) your CSS rules apply to and encourages incorporating Separation of Concerns in your CSS. This makes maintaining and changing your CSS much easier to do, since making a change for one class named using BEM will not inadvertently and unexpectedly break or change the presentation of other elements.

Definitions:

  • Block: A name that describes an HTML element that can be independently moved around in the HTML document and not lose it’s meaning.
  • Element: A name that describes an HTML element that is a child or descendant of a Block element and depends on that Block element to retain it’s meaning.
  • Modifier:  A name that describes an alternate state that an element can get into. For ex., a disabled state.

BEM IN ACTION:

Let’s say you have a form that is part of a login page on your website.  We want to use BEM to construct class names for various elements to apply CSS.
The following is an example using the BEM naming convention to apply class names to the various elements:

<form class="loginForm">
     <input type="text" name="username" class="loginForm__username-input" />
     <input type="password" name="password" class="loginForm__password-input" />
     <button type="submit" class="loginForm__submit-button--disabled">Log in</button>
</form>

loginForm is the Block element class name because the form that it labels can be moved anywhere in the document and still retain it’s meaning – it is the form specifically for logging in.

The username and password inputs are not independent of the login form, so they are labeled using the Element name part in the class name.  This part is preceded by the Block they depend on (loginForm) followed by a double underscore, so they are labelled with descriptive class names such as: loginForm__username-input.

The submit button of the form starts out in a disabled state, so we add a Modifier name to the class name we will use to set the CSS for it.  The Modifier name describes the state of the element preceded by two dashes, the Element it belongs to and the Block followed by two underscores: loginForm__submit-button--disabled.


FURTHER READING AND RESOURCES: 

  • Cleancoders.com – The “Clean Code in the Browser” Series is an excellent video course which demonstrates not only how to use BEM for clean CSS, but how to apply Clean Code and SOLID principles to your HTML/CSS and JavaScript code.  This blog post was based in part on the video discussing BEM.

Data Directed Programming – A Useful Pattern

A common problem in software development is how to deal with multiple but generally similar objects that differ in some way requiring handling, processing and application behavior specific to their subtype.  After going through a free MIT lecture series taught by  Hal Abelson and  Gerald Jay Sussman on the Structure and Interpretation of Computer Programs, I came away learning a very useful software design pattern that I have used in multiple projects now to deal with this situation.

In the lectures, it is sometimes called “Data Directed Programming” or “Dispatch on Type”, which is a way of handling multiple objects that may be related and the same in some general sense, but differ slightly requiring targeted processing and handling for them.  Think of cars like Hondas, Toyotas and Fords;  They are all cars and similar in a general sense – they all have brakes, transmissions and tires etc., but you might need to take a specific make to a specialized mechanic to perform certain repairs, because the specifics (i.e. the implementation) of those components can differ requiring special knowledge in order to work on them.

In software development you will at some point inevitably run into a situation where you are dealing with objects of a general type that require different processing based on their subtype.  This usually leads to all sorts of conditional gymnastics and if/else branches that can become complicated and difficult to understand and maintain.  That’s where this pattern can come in to save the day, keep your code clean and get you out of that mess.

Data Directed Programming – The Basic Pattern:

  • Keep a table or dictionary of types that map to procedures.
  • When you call a general procedure that would apply to a series of related objects with different subtypes, use the type as a lookup to find the corresponding behavior.

Let’s try to make the pattern concrete with an example (forgive me if it’s a little contrived, but it will get the point across and you will recognize when to use this pattern in the real world).  Imagine we have a chat app and we want to process messages that have the same overall shape, but can fall into subtypes such as Private, Broadcast or Group typed messages which differ in a relatively small way and require different handling.

We want to have a single re-usable procedure (or function) that handles message processing – one that we can call when a message comes in.  Let’s see how this looks without using Data Directed Programming (we’ll use vanilla JavaScript for the examples):

// General reusable message processing procedure
const onMessageSent = (message) => {
  // check if message subtype is private and handle that specific processing:
  if (message.type === 'private') {
    ...private message specific processing
  } else if (message.type === 'broadcast') {
    ...broadcast message specific processing
  } else if (message.type === 'group') {
    ...group message specific processing
  } else { 
    throw Error('Message type not recognized.');
  }
};

Note the multiple conditional branches we need to handle the different subtypes of messages appropriately.  It’s not horrible in this contrived example, but it can quickly get out of hand in real world situations and is generally just a bad idea to have complex if/else chains if they can be avoided.  This is where our pattern helps out to eliminate that.

If we make a table and use the message subtype as a primary ID key, we can just pass in the subtype and use the procedure that maps to it directly without having to make conditional branches in our code.

// Make a table, i.e. a dictionary, with keys of message subtypes that map to specialized procedures for that type:
const processMessage = {
  private: (msg) => ...private msg processing,
  broadcast: (msg) => ...broadcast msg processing,
  group: (msg) => ...group msg processing
};

// Our general message processing handler uses the dictionary and looks up the processing we want by subtype, then calling the processing procedure:
const onMessageSent = (message) => {  
  processMessage[message.type](message);
};

As you can see, this is quite a bit cleaner and we’ve gotten rid of all those conditional branches that can be difficult to read and understand.  In my opinion,  good software design and clean code can be based on this one question: Will someone who has never seen this code before be able to understand it quickly and easily?

I can’t stress enough how useful this pattern has been in many of the applications and products I’ve worked on.  This is a common scenario that too often ends up with complicated conditional branching. Obviously you don’t want to overuse any one pattern, but I have found it works well when this common situation arises.

Further Resources:

React-Native – Fixing missing GoogleMaps API Key error

The Problem:

After pushing my Android app built with React-Native and Expo, HereHere, to the Google Play Store recently, the app froze when accessing a page that uses the Google Maps and Google Places API.  After looking at the logs, I noticed an error message indicating a missing API Key in the Android Manifest.  The error stated that android.config.googleMaps.apiKey is missing.

The Solution:

It turns out that after upgrading my app to use the latest version of the Expo SDK, there is a required config variable for the Google Maps API that needs to be set in your app.json file.

*NOTE: My project was built using Expo’s managed workflow.  If you built your project without Expo, you may need to look at updating your AndroidManifest.xml file instead and making sure the API key is set there.

{
  "expo": {
    // other props...,
    "android": {
      ...,
      "config": {
        "googleMaps": {
           "apiKey": "[your api key here]"
        }
      },
      "package": "...",
      "permissions": [...]
    },
    "sdkVersion": "38.0.0"
  }
}

The tricky thing about this was that the app worked fine in development and the problem only arose after I pushed to the Google Play Store and downloaded the app to my phone.  The reason was that my app was using a local config file to get the API key, and the production build looks for the value in app.json to inject into the Android Manifest file.

To try to prevent these kinds of problems in the future, you should publish your application to Expo first using the expo-cli tools ($ expo publish).  You can then access the app in a more production like environment and see if any issues arise before pushing to the Google Play Store to find out about it only when it goes live.

Further Resources:

STOCK GLASSES – Focused and Simplified Stock Analysis

“Everything should be made as simple as possible, but no simpler.”

-Albert Einstein

I’ve been working on a project to play around with Python and Flask and it snowballed into a full blown web app which I’ve deployed.

The app is called Stock Glasses, and it’s purpose is to provide a tool for focused and simplified stock analysis based on concepts in respected financial investing books such as “The Intelligent Investor” by Benjamin Graham and “The Intelligent Asset Allocator” by William Bernstein.

Use the working app here – https://stock-glasses.com

Follow on Twitter: @GlassesStock

Also see the Stock Glasses YouTube Channel for demonstrations of using the app to analyze stocks.

Inspired by First Principles thinking and the quote by Einstein above, the goal is to eliminate noise and focus on the essentials to evaluate a company.  Basically the app focuses on analysis of these fundamentals:

  1. Top Line – Revenue
  2. Bottom Line – Profit/Net Income
  3. Financial Health – Current Assets vs. Total Debt

For a full explanation of how to use the app, visit the How To Use Page of the app.

Future plans for this project include turning it into a progressive web app for offline use and playing around with comparison analysis further.

The app was built with React/Redux, Python Flask and utilizes Firestore for persisting data.

I welcome any feedback, particularly from finance people who might be able to  critique or offer suggestions/improvements on the metrics and analysis tools offered by the app.

Usual disclaimers apply: I’m not a financial advisor or pro investor, so use and invest at your own risk.  I do think it is a potentially useful tool for learning about basic stock analysis, understanding some common metrics and a good introduction to Value Investing.

Additional resources which inspired the app:

 

 

How to Get Socket.io to work with React-Native

After a frustrating couple of days trying to get socket.io to connect to a web socket on my Node/Express server for my project HereHere (place based chat app – please check it out if interested), I finally found out how to make it play nice with React-Native (v0.57).  After reading through numerous posts, GitHub issue threads and blog articles, I thought it would be nice to have one place where you can go to get socket.io working with your React-Native project, so this is just a quick post on how to get it set up and working.

Key points:

  • You need to use socket.io-client on the react-native client side of things
  • The latest version of socket.io-client 2.2.0 does not work with React-Native currently apparently, so you need to install and use version 2.1.1
  • In development, the connection URL on the client side may need to be set to one of three possible values: http://localhost:<serverPort>, http://127.0.0.1:<serverPort>, http://<your IP assigned by your router>:<serverPort>, or if using an Android emulator (I did not need to do this, but it was recommended on a Stack Overflow post, so try it if all else fails) http://10.0.2.2:<serverPort>
  • In production, the connection URL on the client side needs to be pointing to the URL where your server is hosted that handles websocket connections: https://yourserverurl.com
  • Note: I am using Expo to bootstrap my React-Native project.
  • I am also using a Node.js server built with Express on the back end.

Steps:

    1. Install socket.io-client v2.1.1 in your react-native client side project.
      $ npm install [email protected]
    2. In your main App.js file or wherever you would rather initialize socket.io, import the library:
      import socketIO from 'socket.io-client';
    3. Connect to a Web Socket connection to your server by assigning a const to a call to the imported library function and pass in the url to your server (see the possible values in key points above!!) .  Also, pass in some config params, the most important of which is the `transports` key set to `websocket`.
    4. Connect the socket with .connect() and and add a listener to confirm the connection with a console.log.
      Note: initialize socketIO in componentDidMount() in your main App.js file for your React-Native app.  You could extract the code to a module file and call it ‘startSocketIO.js’ to make things cleaner in App.js.

In App.js (your main app component on the client):

import React from 'react';
import { Platform, StatusBar, StyleSheet, View } from 'react-native';
import { startSocketIO } from './services/socketIO';
// ...any other imports needed

export default class App extends React.Component {
  state= {
    isLoadingComplete:false,
  };
componentDidMount() { 
  const socket = socketIO('http://192.168.0.8:5000', {      
  transports: ['websocket'], jsonp: false });   
  socket.connect(); 
  socket.on('connect', () => { 
    console.log('connected to socket server'); 
  }); 
}

// In this case, note that I used the IP address for my machine assigned by the router (in Network Connection details).  This got it working in my development environment.

For further clarity here is an example of some of the code in my Socket IO module where I initialize and use the library.  I export the module to be used in componentDidMount() in my App.js component (top level App component):

 

import socketIO from 'socket.io-client';
...other imports like config file containing Server Url, any secrets, and actions...
// Initialize Socket IO:
const socket = socketIO(config.SERVER_URL, {
  transports: ['websocket'],
  jsonp: false
});

// export the function to connect and use socket IO:
export const startSocketIO = (store) => {
  socket.connect();
  
  socket.on('connect', () => {
    const { userId } = store.getState().user;
  
  socket.on('disconnect', () => {
    console.log('connection to server lost.');
  });
  
  socket.on('newMessage', (message) => {
    store.dispatch(storePublicMessages([ message ]));
  });
};


// In App.js:
import startSocketIO from ...

export default class App extends React.Component {
  componentDidMount() {
    startSocketIO(store);
  }

  render() {
      return (
        <Provider store={store}>
          <View style={styles.container}>  
            <AppNavigator />
          </View>
        </Provider>
      );
    }
  }
}

SETTING UP SOCKETIO IN PRODUCTION:

In order to get it going in Production, you need to pass in the domain URL pointing to where you are hosting your server.  In my case I hosted a Node/Express server on Heroku:

In App.js inside componentDidMount():

const socket = socketIO('https://myserver.onheroku.com', {
  transports: ['websocket'], 
  jsonp: false 
}); 
socket.connect(); 
socket.on('connect', () => { 
  console.log('connected to socket server'); 
});

This was mainly about getting things setup to work on the client side with React-Native, but just for completeness sake, here is a quick snippet of my server.js file which sets up an Express server and socket.io.  Note that on the server side I am using the socket.io library (the latest version) and not socket.io-client.  Also note, that you need to use the builtin https Node module to create a server which you pass the express app into, and then pass that server to socketIO.

Example Main Server file in back end code of the project:

const http = require('http');
const express = require('express');
const socketIO = require('socket.io');
const mongoose = require('mongoose');
const keys = require('./config/keys');
require('./models/Message');

const PORT = process.env.PORT || 5000;
mongoose.connect(keys.mongoURI);

const app = express();
const server = http.createServer(app);

const io = socketIO(server);
io.on('connection', socket => {
  console.log('client connected on websocket');
});

server.listen(PORT, () => {
  console.log('server started and listening on port ' + PORT);
});

Hopefully, this will save you some headache and time pouring through Stack Overflow and posts trying to figure out why your socket IO connection is not working in React-Native.  If you have any questions, comments or suggestions feel free to post them in the comments section and I’ll help with whatever I can.