Skip to main content

In Search for Winglang Middleware Part One

· 23 min read
Asher Sterkin

Introducing a new programming language that creates an opportunity and an obligation to reevaluate existing methodologies, solutions, and the entire ecosystem—from language syntax and toolchain to the standard library through the lens of first principles.

Simply lifting and shifting existing applications to the cloud has been broadly recognized as risky and sub-optimal. Such a transition tends to render applications less secure, inefficient, and costly without proper adaptation. This principle holds for programming languages and their ecosystems.

Currently, most cloud platform vendors accommodate mainstream programming languages like Python or TypeScript with minimal adjustments. While leveraging existing languages and their vast ecosystems has certain advantages—given it takes about a decade for a new programming language to gain significant traction—it's constrained by the limitations of third-party libraries and tools designed primarily for desktop or server environments, with perhaps a nod towards containerization.

Winglang is a new programming language pioneering a cloud-oriented paradigm that seeks to rethink the cloud software development stack from the ground up. My initial evaluations of Winglang's syntax, standard library, and toolchain were presented in two prior Medium publications:

  1. Hello, Winglang Hexagon!: Exploring Cloud Hexagonal Design with Winglang, TypeScript, and Ports & Adapters
  2. Implementing Production-grade CRUD REST API in Winglang: The First Steps

Capitalizing on this exploration, I will focus now on the higher-level infrastructure frameworks, often called 'Middleware'. Given its breadth and complexity, Middleware development cannot be comprehensively covered in a single publication. Thus, this publication is probably the beginning of a series where each part will be published as new materials are gathered, insights derived, or solutions uncovered.

Part One of the series, the current publication, will provide an overview of Middleware origins and discuss the current state of affairs, and possible directions for Winglang Middleware. The next publications will look at more specific aspects.

With Winglang being a rapidly evolving language, distinguishing the core language features from the third-party Middleware built atop this series will remain an unfolding narrative. Stay tuned.

Acknowledgments

Throughout the preparation of this publication, I utilized several key tools to enhance the draft and ensure its quality.

The initial draft was crafted with the organizational capabilities of Notion's free subscription, facilitating the structuring and development of ideas.

For grammar and spelling review, the free version of Grammarly proved useful for identifying and correcting basic errors, ensuring the readability of the text.

The enhancement of stylistic expression and the narrative coherence checks were performed using the paid version of ChatGPT 4.0.

I owe a special mention to Nick Gal’s informative blog post for illuminating the origins of the term "Middleware," helping to set the correct historical context of the whole discussion.

While these advanced tools and resources significantly contributed to the preparation process, the concepts, solutions, and final decisions presented in this article are entirely my own, for which I bear full responsibility.

What is Middleware?

The term "Middleware" passed a long way from its inception and formal definitions to its usage in day-to-day software development practices, particularly within web development.

Covering every nuance and variation of Middleware would be long a journey worthy of a comprehensive volume entitled “The History of Middleware”—a volume awaiting its author.

In this exploration, we aim to chart the principal course, distilling the essence of Middleware and its crucial role in filling the gap between basic-level infrastructure and the practical needs of cloud-based applications development.

Origins of Middleware

Brian RanellThe concept of Middleware ||traces its roots back to an intriguing figure: the Russian-born British cartographer and cryptographer, Alexander d’Agapeyeff, at the "1968 NATO Software Engineering Conference."

Despite the scarcity of official information about d’Agapeyeff, his legacy extends beyond the enigmatic d’Agapeyeff Cipher, as he also played a pivotal role in the software industry as the founder and chairman of the "CAP Group." Insights into the early days of Middleware are illuminated by Brian Randell, a distinguished British computer scientist, in his recounting of "Some Middleware Beginnings."

At the NATO Conference d’Agapeyeff introduced his Inverted Pyramid—a conceptual framework positioning Middleware as the critical layer bridging the gap between low-level infrastructure (such as Control Programs and Service Routines) and Application Programs:

Fig 1: Alexander d'Agapeyeff's Pyramid

Here is how A. d’Agapeyeff explains it:

An example of the kind of software system I am talking about is putting all the applications in a hospital on a computer, whereby you get a whole set of people to use the machine. This kind of system is very sensitive to weaknesses in the software, particular as regards the inability to maintain the system and to extend it freely.

This sensitivity of software can be understood if we liken it to what I will call the inverted pyramid... The buttresses are assemblers and compilers. They don’t help to maintain the thing, but if they fail you have a skew. At the bottom are the control programs, then the various service routines. Further up we have what I call middleware.

This is because no matter how good the manufacturer’s software for items like file handling it is just not suitable; it’s either inefficient or inappropriate. We usually have to rewrite the file handling processes, the initial message analysis and above all the real-time schedulers, because in this type of situation the application programs interact and the manufacturers, software tends to throw them off at the drop of a hat, which is somewhat embarrassing. On the top you have a whole chain of application programs.

The point about this pyramid is that it is terribly sensitive to change in the underlying software such that the new version does not contain the old as a subset. It becomes very expensive to maintain these systems and to extend them while keeping them live.

A. d'Agapeyeff emphasized the delicate balance within this pyramid, noting how sensitive it is to changes in the underlying software that do not preserve backward compatibility. He also warned against danger of over-generalized software too often unsuitable to any practical need:

In aiming at too many objectives the higher-level languages have, perhaps, proved to be useless to the layman, too complex for the novice and too restricted for the expert.

Despite improvements in general-purpose file handling and other advancements since d’Agapeyeff's time, the essence of his observations remains relevant.

There is still a big gap between low-level infrastructure, today encapsulated in an Operating System, like Linux, and the needs of final applications. The Operating System layer reflects and simplifies access to hardware capabilities, which are common for almost all applications.

Higher-level infrastructure needs, however, vary between different groups of applications: some prioritize minimizing the operational cost, some others - speed of development, and others - highly tightened security.

Different implementations of the Middleware layer are intended to fill up this gap and to provide domain-neutral services that are better tailored to the non-functional requirements of various groups of applications.

This consideration also explains why it’s always preferable to keep the core language, aka Winglang, and its standard library relatively small and stable, leaving more variability to be addressed by these intermediate Middleware layers.

Patterns, Frameworks, and Middleware

The middleware definition was refined in the “Patterns, Frameworks, and Middleware: Their Synergistic Relationships” paper, published in 2003 by Douglas C. Schmidt and Frank Buschmann. Here, they define middleware as:

software that can significantly increase reuse by providing readily usable, standard solutions to common programming tasks, such as persistent storage, (de)marshaling, message buffering and queueing, request demultiplexing, and concurrency control. Developers who use middleware can therefore focus primarily on application-oriented topics, such as business logic, rather than wrestling with tedious and error-prone details associated with programming infrastructure software using lower-level OS APIs and mechanisms.

To understand the interplay between Design Patterns, Frameworks and Middleware, let’s start with formal definitions derived from the “Patterns, Frameworks, and Middleware: Their Synergistic Relationships” paper Abstract:

Patterns codify reusable design expertise that provides time-proven solutions to commonly occurring software problems that arise in particular contexts and domains.

Frameworks provide both a reusable product-line architecture – guided by patterns – for a family of related applications and an integrated set of collaborating components that implement concrete realizations of the architecture.

Middleware is reusable software that leverages patterns and frameworks to bridge the gap between the functional requirements of applications and the underlying operating systems, network protocol stacks, and databases.

In other words, Middleware is implemented in the form of one or more Frameworks, which in turn apply several Design Patterns to achieve their goals including future extensibility. Exactly this combination, when implemented correctly, ensures Middleware's ability to flexibly address the infrastructure needs of large yet distinct groups of applications.

Let’s take a closer look at the definitions of each element presented above.

Design Patterns

In the realm of software engineering, a Software Design Pattern is understood as a generalized, reusable blueprint for addressing frequent challenges encountered in software design. As defined by Wikipedia:

In software engineering, a software design pattern is a general, reusable solution to a commonly occurring problem within a given context in software design. It is not a finished design that can be transformed directly into source or machine code. Rather, it is a description or template for how to solve a problem that can be used in many different situations. Design patterns are formalized best practices that the programmer can use to solve common problems when designing an application or system.

Sometimes, the term Architectural Pattern is used to distinguish high-level software architecture decisions from lower-level, implementation-oriented Design Patterns, as defined in Wikipedia:

An architectural pattern is a general, reusable resolution to a commonly occurring problem in software architecture within a given context. The architectural patterns address various issues in software engineering, such as computer hardware performance limitations, high availability and minimization of a business risk. Some architectural patterns have been implemented within software frameworks.

It is essential to differentiate Architectural and Design Patterns from their implementations in specific software projects. While an Architectural or Design Pattern provides an initial idea for solution, its implementation may involve a combination of several patterns, tailored to the unique requirements and nuances of the project at hand.

Architectural Patterns, such as Pipe-and-Filters, and Design Patterns, such as the Decorator, are not only about solving problems in code. They also serve as a common language among architects and developers, facilitating more straightforward communication about software structure and design choices. They are also invaluable tools for analyzing existing solutions, as we will see later.

Software Frameworks

In the domain of computer programming, a Software Framework represents a sophisticated form of abstraction, designed to standardize the development process by offering a reusable set of libraries or tools. As defined by Wikipedia:

In computer programming, a software framework is an abstraction in which software, providing generic functionality, can be selectively changed by additional user-written code, thus providing application-specific software.

It provides a standard way to build and deploy applications and is a universal, reusable software environment that provides particular functionality as part of a larger software platform to facilitate the development of software applications, products and solutions.

In other words, a Software Framework is an evolved Software Library that employs the principle of inversion of control. This means the framework, rather than the user's application, takes charge of the control flow. The application-specific code is then integrated through callbacks or plugins, which the framework's core logic invokes as needed.

Utilizing a Software Framework as the foundational layer for integrating domain-specific code with the underlying infrastructure allows developers to significantly decrease the development time and effort for complex software applications. Frameworks facilitate adherence to established coding standards and patterns, resulting in more maintainable, scalable, and secure code.

Nonetheless, it's crucial to follow the Clean Architecture guidelines, which mandate that domain-specific code remains decoupled and independent from any framework to preserve its ability to evolve independently of any infrastructure. Therefore, an ideal Software Framework should support plugging into it a pure domain code without any modification.

Middleware

The Middleware is defined by Wikipedia as follows:

Middleware is a type of computer software program that provides services to software applications beyond those available from the operating system. It can be described as "software glue".

Middleware in the context of distributed applications is software that provides services beyond those provided by the operating system to enable the various components of a distributed system to communicate and manage data. Middleware supports and simplifies complex distributed applications. It includes web servers, application servers, messaging and similar tools that support application development and delivery. Middleware is especially integral to modern information technology based on XML, SOAP, Web services, and service-oriented architecture.

Middleware, however, is not a monolithic entity but is rather composed of several distinct layers as we shall see in the next section.

Middleware Layers

Below is an illustrative diagram portraying Middleware as a stack of such layers, each with its specialized function, as suggested in the Schmidt and Buchman paper:

Fig 2: Middleware Layers

Fig 2: Middleware Layers in Context

Layered Architecture Clarified

To appreciate the significance of this layered structure, a good understanding of the very concept of Layered Architecture is essential—a concept too often misunderstood completely and confused with the Multitier Architecture, deviating significantly from the original principles laid out by E.W. Dijkstra.

At the “1968 NATO Software Engineering Conference,” E.W. Dijkstra presented a paper titled “Complexity Controlled by Hierarchical Ordering of Function and Variability” where he stated:

We conceive an ordered sequence of machines: A[0], A[1], ... A[n], where A[0] is the given hardware machine and where the software of layer i transforms machine A[i] into A[i+1]. The software of layer i is defined in terms of machine A[i], it is to be executed by machine A[i], the software of layer i uses machine A[i] to make machine A[i+1].

In other words, in a correctly organized Layered Architecture, the higher-level virtual machine is implemented in terms of the lower-level virtual machine. Within this series, we will come back to this powerful technique over and over again.

Back to the Middleware Layers

Right beneath the Applications layer resides the Domain-Specific Middleware Services layer, a notion deserving a separate discussion within the broader framework of Domain-Driven Design.

Within this context, however, we are more interested in the Distribution Middleware layer, which serves as the intermediary between Host Infrastructure Middleware within a single "box" and the Common Middleware Services layer which operates across a distributed system's architecture.

As stated in the paper:

Common middleware services augment distribution middleware by defining higher-level domain-independent reusable services that allow application developers to concentrate on programming business logic.

With this understanding, we can now place Winglang Middleware within the Middleware Services layer enabling the implementation of Domain-Specific Middleware Services in terms of its primitives.

To complete the picture, we need more quotes from the “Patterns, Frameworks, and Middleware: Their Synergistic Relationships” article mapped onto the modern cloud infrastructure elements.

Host Infrastructure Middleware

Here is how it’s defined in the paper:

Host infrastructure middleware encapsulates and enhances native OS mechanisms to create reusable event demultiplexing, interprocess communication, concurrency, and synchronization objects, such as reactors; acceptors, connectors, and service handlers; monitor objects; active objects; and service configurators. By encapsulating the peculiarities of particular operating systems, these reusable objects help eliminate many tedious, error-prone, and non-portable aspects of developing and maintaining application software via low-level OS programming APIs, such as Sockets or POSIX pthreads.

In the AWS environment, general-purpose virtualization services such as AWS EC2 (computer), AWS VPC (network), and AWS EBS (storage) play this role.

On the other hand, when speaking about the AWS Lambda execution environment, we may identify AWS Firecracker, AWS Lambda standard and custom Runtimes, AWS Lambda Extensions, and AWS Lambda Layers as also belonging to this category.

Distribution Middleware

Here is how it’s defined in the paper:

Distribution middleware defines higher-level distributed programming models whose reusable APIs and objects automate and extend the native OS mechanisms encapsulated by host infrastructure middleware.

Distribution middleware enables clients to program applications by invoking operations on target objects without hard-coding dependencies on their location, programming language, OS platform, communication protocols and interconnects, and hardware.

Within the AWS environment, fully managed API, Storage, and Messaging services such as AWS API Gateway, AWS SQS, AWS SNS, AWS S3, and DynamoDB would fit naturally into this category.

Common Middleware Services

Here is how it’s defined in the paper:

Common middleware services augment distribution middleware by defining higher-level domain-independent reusable services that allow application developers to concentrate on programming business logic, without the need to write the “plumbing” code required to develop distributed applications via lower-level middleware directly.

For example, common middleware service providers bundle transactional behavior, security, and database connection pooling and threading into reusable components, so that application developers no longer need to write code that handles these tasks.

Whereas distribution middleware focuses largely on managing end-system resources in support of an object-oriented distributed programming model, common middleware services focus on allocating, scheduling, and coordinating various resources throughout a distributed system using a component programming and scripting model.

Developers can reuse these component services to manage global resources and perform common distribution tasks that would otherwise be implemented in an ad hoc manner within each application. The form and content of these services will continue to evolve as the requirements on the applications being constructed expand.

Formally speaking, Winglang, its Standard Library, and its Extended Libraries collectively constitute Common middleware services built on the top of the cloud platform Distribution Middleware and its corresponding lower-level Common middleware services represented by the cloud platform SDK for JavaScript and various Infrastructure as Code tools, such as AWS CDK or Terraform.

With Winglang Middleware we are looking for a higher level of abstraction built in terms of the core language and its library and facilitating the development of production-grade Domain-specific middleware services and applications on top of it.

Domain-Specific Middleware Services

Here is how it’s defined in the paper:

Domain-specific middleware services are tailored to the requirements of particular domains, such as telecom, e-commerce, health care, process automation, or aerospace. Unlike the other three middleware layers discussed above that provide broadly reusable “horizontal” mechanisms and services, domain-specific middleware services are targeted at “vertical” markets and product-line architectures. Since they embody knowledge of a domain, moreover, reusable domain-specific middleware services have the most potential to increase the quality and decrease the cycle-time and effort required to develop particular types of application software.

To sum up, the Winglang Middleware objective is to continue the trend of the Winglang compiler and its standard library to make developing Domain-specific middleware services less difficult.

Cloud Middleware State of Affairs

Applying the terminology introduced above, the current state of affairs with AWS cloud Middleware could be visualized as follows:

Fig 3: Cloud Middleware State of Affairs

We will look at three leading Middleware Frameworks for AWS:

  1. Middy (TypeScript)
  2. Power Tools for AWS Lambda (Python, TypeScript, Java, and .NET)
  3. Lambda Middleware

Middy

If we dive into the Middy Documentation we will find that it positions itself as a middleware engine, which is correct if we recall that very often Frameworks, which Middy is, are called Engines. However, it later claims that “… like generic web frameworks (fastify, hapi, express, etc.), this problem has been solved using the middleware pattern.” This is, as we understand now, complete nonsense. If we dive into the Middy Documentation further, we will find the following picture:

Fig 4: Middy

Now, we realize that what Middy calls “middleware” is a particular implementation of the Pipe-and-Filters Architecture Pattern via the Decorator Design Pattern. The latter should not be confused with TypeScript Decorators. In other words, Middy decorators are assembled into a pipeline each one performing certain operations before and/or after an HTTP request handling.

Perhaps, the main culprit of this confusion is the expressjs Framework Guide usage of titles like “Writing Middleware” and “Using Middleware” even though it internally uses the term middleware function, which is correct.

Middy comes with an impressive list of official middleware decorator plugins plus a long list of 3rd party middleware decorator plugins.

Power Tools for AWS Lambda

Here, the basic building blocks are called Features, which in many cases are Adapters of lower-level SDK functions. The list of features for different languages varies with the Python version to have the most comprehensive one. Features could be attached to Lambda Handlers using language decorators, used manually, or, in the case of TypeScript, using Middy. The term middleware pops up here and there and always means some decorator.

Lambda Middleware

This one is also an implementation of the Pipe-and-Filters Architecture Pattern via the Decorator Design Pattern. Unlike Middy, individual decorators are combined in a pipeline using a special Compose decorator effectively applying the Composite Design Pattern.

Limitations of existing solutions

Apart from using the incorrect terminology, all three frameworks have certain limitations in common, as follows:

  1. The confusing sequence of operation of multiple Decorators. When more than one decorator is defined, the sequence of before operations is in the order of decorators, but the the sequence of after operations is in reverse order. With a long list of decorators that might be a source of serious confusion or even a conflict.

  2. Reliance of environment variables. Control over the operation of particular adapters (e.g. Logger) solely relies on environment variables. To make a change, one will need to redeploy the Lambda Function.

  3. A single list of decorators with some limited control in runtime. There is only one list of decorators per Lambda Function and, if some decorators need to be excluded and replaced depending on the deployment target or run-time environment, a run-time check needs to be performed (look, for example, at how Tracer behavior is controlled in Power Tools for AWS Lambda). This introduces unnecessary run-time overhead and enlarges the potential security attack surface.

  4. Lack of support for higher-level crosscut specifications. All middleware decorators are specified for individual Lambda functions. Common specifications at the organization, organization unit, account, or service levels will require some handmade custom solutions.

  5. Too narrow interpretation of Middleware as a linear implementation of Pipe-and-Filers and Decorator design patterns. Power Tools for AWS Lambda makes it slightly better by introducing its Features, also called Utilities, such as Logger, first and corresponding decorators second. Middy, on the other hand, treats everything as a decorator. In both cases, the decorators are stacked in one linear sequence, such that retrieving two parameters, one from the Secrets Manager and another from the AppConfig, cannot be performed in parallel while state-of-the-art pipeline builders, such as Marble.js and Async.js, support significantly more advanced control forms.

For Winglang Common Services Middleware Framework (we can now use the correct full name) this list of limitations will serve as a call for action to look for pragmatic ways to overcome these limitations.

Winglang Middleware Direction

Following the “Patterns, Frameworks, and Middleware: Their Synergistic Relationships” article middleware layers taxonomy, the Winglang Common Middleware Services Framework is positioned as follows:

Fig 5: Winglang Middlware Layer

In the diagram above, the Winglang Middleware Layer, code name Winglang MW, is positioned as an upper sub-layer of Common Middleware Services, built on the top of the Winglang as a representative of the Infrastructure-from-Code solution, which in turn is built on the top of the cloud-specific SDK and IaC solutions providing convenient access to the cloud Distribution Middleware.

From the feature set perspective, the Winglang MW is expected

  1. To be on par with leading middleware frameworks such
    1. Middy (TypeScript)
    2. Power Tools for AWS Lambda (Python, TypeScript, Java, and .NET)
    3. Lambda Middleware
  2. In addition, to provide support for leading open standards such as
    1. OpenID
    2. Open Telemetry
    3. OAuth 2.0
    4. Async API
    5. Cloud Events
  3. To provide built-in support for cross-cut middleware specifications at different levels above individual cloud functions
  4. To support run-time fine-tuning of individual feature parameters (e.g. logging level) without corresponding cloud resources redeployment

Different implementations of Winglang MW will vary in efficiency, ease of use (e.g. middleware pipeline configuration), flexibility, and supplementary tooling such as automatic code generation.

At the current stage, any premature conversion towards a single solution will be detrimental to the Winglang ecosystem evolution, and running multiple experiments in parallel would be more beneficial. Once certain middleware features prove themselves, they might be incorporated into the Winglang core, but it’s advisable not to rush too fast.

For the same reason, I intentionally called this section Directions rather than Requirements or Problem Statement. I have only a general sense of the desirable direction to proceed. Making things too specific could lead to some serendipitous alternatives being missed. I can, however, specify some constraints, as follows:

  1. Do not count on advanced Winglang features, such as Generics, to come. Generics may significantly complicate the language syntax and too often are introduced before a clear understanding of how much sophistication is required. Also, at the early stages of exploration, the lack of Generics support could be compensated by switching to a general-purpose data type, such as Json, or code generators, including the “C” macros.
  2. Stick with Winglang and switch to TypeScript for implementing low-level extensions only. As a new language, Winglang lacks features taken for granted in mainstream languages and therefore requires some faith to get a fair chance to write as much code as possible, even if it is slightly less convenient. This is the only way for a new programming language to evolve.
  3. If the development of CLI tools is required, prefer TypeScript over other languages such as Python. I already have the TypeScript toolchain installed on my desktop with all dependencies resolved. It’s always better to limit the number of moving parts in the system to the absolute minimum.
  4. Limit Winglang middleware implementation to a single process of a Cloud Function. Out-of-proc capabilities, such as AWS Lambda Extensions, can improve overall system performance, security, and reuse (see, for example, this blog post). However, they are not currently supported by Winglang out of the box. Also, utilizing such advanced capabilities will increase the system's complexity while contributing little, if any, at the semantic level. Exploring this direction can be postponed to later stages.

What’s Next?

This publication was completely devoted to clarifying the concept of Middleware, its position within the cloud software system stack, and defining a general direction for developing one or more Winglang Middleware Frameworks.

I plan to devote the next Part Two of this series to exploring different options for implementing the Pipe-and-Filters Pattern in Middleware and after that to start building individual utilities and corresponding filters one by one.

It’s a rare opportunity that one does not encounter every day to revise the generic software infrastructure elements from the first principles and to explore the most suitable ways of realizing these principles on the leading modern cloud platforms. If you are interested in taking part in this journey, drop me a line.