Posts

Introducing Notation

Daniel Grant profile photo
Daniel Grant
7 July 2023

This week I released a preview of a project that I’ve been working on.

Notation is a backend framework that compiles TypeScript to fully functional cloud applications. It frees developers from worrying about infrastructure, and lets them focus on building useful software.

Motivation

The idea emerged a couple of years ago. I was building an analytics platform and chose a monolithic tech stack, based around Postgres. This enabled rapid development, but as the product started growing, so did the list of scaling issues.

I’ve always been hesitant to start projects with a serverless/microservices architecture, and refactoring that analytics project reminded me why – developing cloud-native software has major overheads.

Platforms like AWS and GCP shift the focus away from building features, and onto managing infrastructure. Mastery of these (vendor-specific) skills often requires full-time specialism. Furthermore, getting distributed cloud services to form a coherent software system is inherently complex.

Choosing microservices for a greenfield project may not be the right choice
Choosing microservices for a greenfield project may not be the right choice

So, a proposition was conceived: make developing cloud-native software as productive (and fun) as developing a monolith. If successful, Notation will be the right choice at both the start of a project and as it scales up.

Hello World

Here’s the code for a serverless function that is triggered by an API endpoint:

import { lambda, apiGateway } from "@notation/aws";

export const DoSomething = lambda(async () => {
	// do something
});

const api = apiGateway();

export const PostMessage = api.route({
  method: "POST",
  route: "/message",
  handler: DoSomething,
});

Behind the scenes, Notation infers – based on the relationship between the two resources – that it needs to set up some other resources: a proxy integration, a lambda permission, an IAM role, and a policy attachment.

That’s a lot of configuration – even for this simple example – that the developer didn’t need to manage with infrastructure-as-code.

Type Safety

Application and infrastructure concerns are declared in the same type space. This allows the TypeScript compiler to ensure cloud services are compatible. (For example, the compiler would not allow you to put an Azure Function behind an AWS API Gateway).

Data passed between services can also be typed using a library like Zod.

Distributed Workflows

Serverless functions can be composed in ways that would normally require a library like Kafka or Airflow, or a lot of duct tape.

The programming model for creating workflows is intuitive and programmatic:

import { workflow } from "@notation/azure";
import { GetUsers } from "../functions/get-users.ts";
import { SendSms } from "../functions/send-sms.ts";

export default workflow(() => {
  const users = GetUsers();

  users.batch({ size: 100 }, (user) => {
    SendSms(user);
  });
});

The workflow code is evaluated at build time and generates a static definition of the workflow. In production, this is consumed by a workflow engine to determine how to execute and pass data between serverless functions.

Workflows are a powerful feature that open up a wide variety of use-cases:

  • pipelines for processing events and data
  • integrations for streaming chatbot responses across services
  • automations that enable GDPR compliance
  • media transcoders for optimising images and video

Release

Notation will be released as an open source project. There are lots of interesting challenges ahead, and I’m looking forward to working through them with the developer community.

If this sounds interesting to you, sign up for early access. And, if you have any questions or feedback, drop me a message!