back
September 19, 2023
|
Ecosystem

Apache Kafka vs. RabbitMQ: Comparing architectures, capabilities, and use cases

The main difference between them is that Kafka is an event streaming platform designed to ingest and process massive amounts of data, while RabbitMQ is a general-purpose message broker that supports flexible messaging patterns, multiple protocols, and complex routing.

Graphic featuring Apache Kafka and RabbitMQ logos
Quix brings DataFrames and the Python ecosystem to stream processing. Stateful, scalable and fault tolerant. No wrappers. No JVM. No cross-language debugging.

Our library is open source—support the project by starring the repo.

Introduction

Messaging systems are a foundational element in modern IT architectures, serving as the backbone for data exchange between various applications and services. They decouple components, allowing for flexibility, scalability, and resiliency, and they enable us to implement event-driven, microservice-based architectures. 

This article compares two popular messaging systems: Apache Kafka and RabbitMQ. Before we dive into a detailed analysis of their architectures, features, performance characteristics, and use cases, here are some key takeaways:

  • RabbitMQ is a multi-purpose message broker. It supports several protocols, flexible messaging patterns, and complex routing logic. 
  • Kafka is a distributed event streaming platform designed to handle high-velocity, high-volume streaming data. It’s a good choice for real-time data pipelines and stream processing.
  • RabbitMQ follows a “complex broker, simple consumer” approach, while Kafka has a “simple broker, complex consumer” model.
  • Kafka and RabbitMQ are open source solutions, and there are vendors offering commercial support for both.
  • They’re both fault-tolerant and highly available solutions, but Kafka is better equipped to deal with hyper-scale scenarios (petabytes of data and trillions of messages per day, distributed across hundreds or even thousands of brokers).
  • RabbitMQ and Kafka offer various clients, targeting multiple languages (for instance, Java, Go, Python, PHP, Node.js, .NET, etc.)
  • Kafka offers more integrations and has a larger and more active community.

If you’re here because you’re planning to build an event-driven application, I recommend the “Guide to the Event-Driven, Event Streaming Stack,” which talks about all the components of EDA and walks you through a reference use case and decision tree to help you understand where each component fits in.

What is Apache Kafka?

Apache Kafka is an open source event streaming platform written in Java and Scala. It’s designed to handle high-velocity, high-volume, and fault-tolerant data streams. Kafka was originally developed at LinkedIn and later donated to the Apache Software Foundation. Kafka has quickly become a popular choice for building real-time data pipelines, event-driven architectures, and microservices applications.

What is RabbitMQ?

RabbitMQ is an open source, multi-protocol message broker written in Erlang. It was initially developed by Rabbit Technologies Ltd, and later acquired by SpringSource, a division of VMware. RabbitMQ is a popular choice for enabling message-driven communication in distributed systems and offers flexibility in integrating diverse applications through various messaging patterns (e.g., message queue, pub/sub).  

Kafka vs. RabbitMQ: comparing architectures

We’ll now review Kafka’s and RabbitMQ’s architectures to understand their similarities and differences.

Apache Kafka architecture

At a high level, Kafka's architecture consists of three main elements: producers, consumers, and brokers. Producers write messages to brokers, while consumers read the data ingested by brokers, following the publish/subscribe pattern.

Brokers run on a Kafka cluster, while producers and consumers are entirely decoupled from the system. Each broker stores the actual data sent by producers in topics — collections of messages belonging to the same group/category. Kafka’s topics can be divided into multiple partitions, which brings benefits like fault tolerance, scalability, and parallelism. Each broker can store selected partitions from a topic, while the other partitions can be distributed across other brokers. This approach helps split and balance the workload between brokers.

For enhanced reliability, availability, and fault tolerance, you can set up replicas for each topic’s partitions across a configurable number of brokers. This way, if a broker becomes unavailable, automatic failover to another replica in the cluster is possible, so messages remain available. In addition to intra-cluster replication, you can use MirrorMaker to replicate entire Kafka clusters. These replicated clusters can be located in different data centers or even different regions (geo-replication). 

Kafka's architecture overview.
Kafka’s architecture

In the diagram above, you can also notice a ZooKeeper component, which is responsible for things like:

  • Storing metadata about the Kafka cluster — for instance, information about topics, partitions, brokers, and replicas.
  • Managing and coordinating Kafka brokers, including leader election.
  • Maintaining access control lists (ACLs) for security purposes.

There’s a plan to remove the ZooKeeper dependency starting with Kafka v 4.0 (projected to be released in April 2024). Instead, a new mechanism called KRaft will be used (it’s actually already production-ready). KRaft eliminates the need to run a ZooKeeper cluster alongside every Kafka cluster, and moves the responsibility of metadata management into Kafka itself. This simplifies the architecture, reduces operational complexity, and improves scalability.

RabbitMQ architecture

RabbitMQ employs an architecture that revolves around publishers, consumers, and message brokers. Producers generate messages and send them to brokers, while consumers read the data ingested by brokers. To be more exact, producers publish messages to entities within brokers called exchanges. Then, exchanges route messages to specific queues using rules called bindings. Finally, RabbitMQ brokers deliver messages to consumers subscribed to queues. 

Let’s make an analogy to better understand exchanges, bindings, and queues:

  • An exchange is like a central train station.
  • Bindings are akin to train schedules that determine which platforms trains are directed to.
  • Queues are specific train platforms.

Note that there are several types of exchanges:

  • Direct exchange. Ideal for unicast (point-to-point) routing of messages, using routing keys.
  • Headers exchange. Designed for routing messages to queues based on multiple attributes that are expressed as message headers rather than routing keys. 
  • Fanout exchange. Messages are routed to all of the queues that are bound to the exchange. This type of exchange is the best choice for broadcast use cases. No routing key (message key) is used.
  • Topic exchange. Messages are routed to one or more queues, based on routing keys. Topic exchanges are commonly used to implement publish/subscribe patterns for scenarios where consumers selectively choose which types of messages they want to receive.   
RabitMQ broker architecture scheme.
RabbitMQ's architecture

Note: In addition to queues, starting with version 3.9, RabbitMQ introduced a new type of data structure: streams. A RabbitMQ stream is essentially an append-only log with non-destructive consuming semantics. Unlike queues, consuming from a stream does not remove messages (they can be re-read).  

For high availability and improved throughput, you can deploy RabbitMQ as a cluster, which groups multiple RabbitMQ nodes to form a single logical broker. In addition, you can use a federation of clusters to scale your RabbitMQ setup and distribute the messaging load across multiple brokers.

Kafka vs. RabbitMQ: features

How do Kafka and RabbitMQ compare in terms of messaging capabilities, and data structure and storage?

Messaging capabilities

Attribute Kafka RabbitMQ

Messaging protocols

Kafka uses a custom binary protocol over TCP.

The core protocol used by queues is AMQP (Advanced Message Queuing Protocol) 0-9-1. Queues support other protocols as well: AMQP 1.0, STOMP & MQTT over WebSockets, and HTTP.

RabbitMQ streams use a custom binary protocol.

Data formats

Supports any data format that can be converted to and from a byte array. Common examples include Avro, JSON, and ProtoBuf.

Supports any data format that can be converted to and from a byte array. Common examples include JSON, ProtoBuf, and MessagePack.

Message ordering

Message ordering is guaranteed at partition level.

Message ordering is guaranteed at queue level, provided there is only one consumer. If there are multiple consumers attached to a queue, RabbitMQ cannot ensure message ordering.

Exactly-once semantics

Kafka supports exactly-once semantics. See this article for details.

RabbitMQ does not offer exactly-once semantics. 

Use of acknowledgments guarantees at-least-once delivery. If you don’t use acknowledgments, RabbitMQ only ensures at-most-once semantics.

Message priorities

No native message priority support.

You can set a priority level on a per-message basis (a number between 0-255, with bigger numbers having higher priority), and send messages to a priority queue. The broker will attempt to deliver higher-priority messages before lower-priority ones. 

Message replay

Message replay is a first-class feature.

Kafka can store data for a configurable amount of time, allowing consumers to replay stored messages as needed.

In the case of queues, RabbitMQ can only replay messages that have not been consumed and acknowledged (dead-letter queues are used for this purpose). However, this is more of an exception-handling mechanism than a feature designed for replay.

Message replay is possible when using RabbitMQ streams, which have non-destructive consuming semantics. 

Message routing

Apache Kafka itself doesn’t provide extensive routing capabilities. However, advanced content-based routing is possible via the Kafka Connect and Kafka Streams components.

RabbitMQ provides extensive, flexible routing capabilities via routing keys and exchange types (direct, headers, topic, fanout).

Message consumption

Consumers use a pull model (long polling) to read messages. 

RabbitMQ clients can pull messages, or the broker can push them (the push model is the recommended option).

Messaging patterns

Pub/sub

Pub/sub

Queues

RPC

Broker & consumer type

Simple broker, complex consumer

Complex broker, simple consumer

While there are a few similarities between Kafka and RabbitMQ regarding messaging features, there are also plenty of differences. Both tools support any data format that can be converted to and from a byte array, and they both offer features like message replay and routing (although, Kafka’s feature replay is arguably more advanced, while RabbitMQ has more complex built-in routing). Additionally, both solutions provide guarantees around message ordering.

RabbitMQ offers more flexible messaging capabilities. This is because it comes with several protocols, priority messages, and different messaging patterns. On the other hand, Kafka is better equipped for use cases where data integrity is critical, as it supports exactly-once semantics (unlike RabbitMQ). 

A major difference between them is that Kafka uses a “simple broker, complex consumer” approach, while RabbitMQ follows a “complex broker, simple consumer” model. This means that, with RabbitMQ, developing consumer apps is more straightforward, as most of the complexity resides in the broker. Meanwhile, Kafka’s model means that developing consumer apps is more challenging, but the broker is lightweight, and easier to manage, operate, and scale.

Data structure and storage

Attribute Kafka RabbitMQ

Data structure

Topics (which are divided into partitions)

Queues (RabbitMQ classic)

Streams (RabbitMQ Streams)

Data storage

Log-based storage model using a single, append-only log file for each topic partition.

Messages are written sequentially to the log, which is stored on the broker’s disk. 

In the case of queues, messages are either stored directly on the broker’s disk to ensure persistence, or held in memory as a transient message to optimize disk usage. The persistence layer consists of a message store and a queue index which keeps track of the location of messages within the message store.

Meanwhile, RabbitMQ streams use a log-based storage model, similar to Kafka. Messages are appended sequentially to the end of the log, which is stored on disk.

Long-term persistence

Data can be stored indefinitely if desired.

Queues are not designed for long-term data storage. They persist messages just long enough to ensure they are delivered and processed by consumers (it’s a safeguard against broker failures). 

On the other hand, RabbitMQ streams are designed to handle long-term persistence, similar to Kafka. 

As we can see, there are both similarities and differences between Kafka and RabbitMQ regarding how they handle data. At the time of writing this article, both Kafka and RabbitMQ store data on the broker’s disk. But things will change in the future — there’s a plan to introduce a tiered storage approach for Kafka, with two tiers: local and remote. The local tier will use local disks on Kafka brokers to store data. It’s designed to retain data for short periods (e.g., a few hours). Meanwhile, remote storage will use systems like the Hadoop Distributed File System (HDFS) and Amazon S3 for long-term data storage (days, months, etc.)

Speaking of persistence, this is another difference between Kafka and RabbitMQ. While in Kafka’s case long-term persistence is a key feature (data can be stored indefinitely), with RabbitMQ, storing data for long periods of time is only possible if you use streams.

Kafka vs. RabbitMQ: scalability, performance, reliability

Attribute Kafka RabbitMQ

Throughput

Kafka can reliably handle up to millions of messages per second.

In theory, RabbitMQ can also handle millions of messages per second, but it requires more brokers than Kafka to achieve such a high throughput. 

RabbitMQ is optimized to handle lower throughputs (thousands or tens of thousands of messages per second).

Latency

Very low latency (in the millisecond range).

Very low latency (in the millisecond range). 

Latency increases when high throughput workloads are involved.

Scalability

Kafka can scale horizontally to handle petabytes of data and trillions of messages per day, distributed across hundreds (or even thousands) of brokers.

RabbitMQ can be scaled horizontally, but it’s not designed for the massive scalability you can achieve with Kafka.  

Fault tolerance and availability

Replicates data across multiple nodes for fault tolerance. 

Supports geo-replication across different datacenters and regions.

Replicates data across multiple nodes for fault tolerance (by using quorum queues and streams). 

You can use federations of clusters to move messages between brokers, even if those brokers are in different geographical locations. 

Both Kafka and RabbitMQ are fault-tolerant and highly available solutions. There are, however, some differences when it comes to performance and scalability:

  • Kafka is designed for hyper-scale scenarios, as demonstrated in production by companies like LinkedIn, Twitter, and Netflix. It provides lower latencies at higher throughput.
  • RabbitMQ can achieve lower latency than Kafka when small workloads are involved. However, RabbitMQ latencies degrade as throughput increases. 
  • I couldn’t find any proof that RabbitMQ is geared to the same level of scalability (and performance at scale) as Kafka — not even RabbitMQ streams, which are designed to offer better performance than RabbitMQ queues.  

To learn more about the performance and scalability differences between Kafka and RabbitMQ, check out this benchmark

Kafka vs. RabbitMQ: developer experience and ecosystem

So far, we’ve looked at the differences and similarities between Kafka’s and RabbitMQ’s architecture, features, and performance. But how do they fair in terms of DevEx, community, and ecosystem?

DevEx and community

Attribute Kafka RabbitMQ
GitHub stats (accurate as of 30th of August 2023)

25.7k stars

13k forks

1.1k watching

11k stars

3.9k forks

380 watching

Documentation

Extensive, clear documentation

Extensive documentation, but perhaps not as clear as Kafka’s

Community

Huge and active community

Large community, but not as big and active as Kafka’s

Learning curve

It can take teams between a few days and several weeks to learn the basics of Kafka.

It can take months to master complex concepts.

Similar to Kafka, it can take teams between a few days and a few weeks to learn the basics of RabbitMQ, and months to master it.  

Clients

Wide variety of official and community-made clients, targeting languages and platforms like Java, Scala, Go, Python, C, C++, Ruby, .NET, PHP, Node.js, and Swift.

Wide variety of official and community-made clients, targeting languages and platforms like Erlang, Java, .NET, Ruby, Python, PHP, JavaScript, Go, Swift, and C++.

CLIs

Kafka includes a set of built-in CLI tools that allow you to perform various actions, such as:

  • Create, list, and delete topics.
  • Send and consume messages.
  • List brokers and consumer groups.
  • Retrieve information about Kafka clusters (e.g., version, broker ID).
  • Create and delete access control lists.
  • Register and check schemas.

RabbitMQ ships with multiple command line tools that serve various purposes:

  • Diagnostics and health checking.
  • Maintenance tasks on queues and streams.
  • Plugin management.
  • Service management and general operator tasks.
  • Collecting cluster and environment information, as well as server logs.



Monitoring

Requires setting up monitoring tools (e.g., JMX, Grafana, Prometheus).

The RabbitMQ management plugin provides an HTTP-based API for monitoring RabbitMQ nodes and clusters, along with a browser-based UI for viewing metrics.

You can also set up external tools to monitor your RabbitMQ deployments, such as Prometheus and Grafana.

Open source license

Apache License 2.0

Mozilla Public License Version 2.0 is the main license for RabbitMQ.

Some client-specific SDKs are also licensed under Apache License 2.0

Deployment options (self-managed)

Various deployment options:

  • On-prem
  • Using containers (e.g., Docker)
  • In the cloud (AWS, GCP, Azure, Confluent Platform, Alibaba Cloud, IBM Cloud, etc)
  • Using Kubernetes

Various deployment options:

  • On-prem
  • In the cloud
  • Using Docker
  • Using Kubernetes
  • Using Cloud Foundry BOSH
  • Using Puppet Forge
Commercial support

Numerous third-party vendors provide managed Kafka services.

Examples include Quix, Confluent Cloud, Amazon MSK, Aiven, Instaclustr, and Azure HDInsight.

Plenty of third-party vendors offer managed RabbitMQ services and technical support.

Examples include VMware, CloudAMQP, AmazonMQ, Erlang Solutions, Northflank, and Visual Integrator, Inc

Kafka has the upper hand on RabbitMQ when it comes to community, user base, and educational resources. There are hundreds of Kafka meetups, and tens of Kafka-focused events and conferences worldwide. In addition, there are thousands of blog posts, tutorials, and educational resources related to Kafka, offering a wealth of information on Kafka usage and best practices. In comparison, there are significantly fewer RabbitMQ events, and not as many online resources. Judging by GitHub stats, Kafka’s user base is several times bigger than RabbitMQ’s.

Apache Kafka and RabbitMQ graphic statistics.
There are more search queries for Kafka compared to RabbitMQ. Source: Google Trends

Kafka and RabbitMQ seem rather evenly matched if we compare clients, CLIs, and deployment options. For instance, both solutions provide a good variety of clients, targeting numerous programming languages (learn more about Kafka clients and RabbitMQ clients). Additionally, RabbitMQ and Kafka are open source solutions that can be flexibly deployed in various ways: on-prem, in the cloud, using Docker and Kubernetes, etc.  

It’s worth mentioning that RabbitMQ and Kafka have a rather steep learning curve — it can take months (or even more) to master them. Fortunately, if you want to avoid (some of) the complexity that comes with deploying and managing these two solutions, there are plenty of third-party vendors that you can offload this responsibility to (arguably, Kafka vendors are more numerous and better known). 

Ecosystem

Attribute Kafka RabbitMQ
Integrations

Large ecosystem of integrations, with source and sink connectors that allow Kafka to seamlessly connect to hundreds of other systems

Small ecosystem of integrations, consisting of integrations with databases, and some plugins to extend RabbitMQ’s capabilities

Built-in stream processing

Built-in stream processing capabilities (via Kafka Streams) 

No built-in stream processing capabilities

Kafka has a much larger ecosystem of integrations compared to RabbitMQ. The Kafka Connect framework allows you to easily ingest data from other systems into Kafka, and stream data from Kafka topics to various destinations. There are hundreds of connectors for different types of systems, such as databases (e.g., MongoDB), storage systems (like Azure Blob Storage), messaging systems (for instance, JMS), and many more. Kafka even provides sync and source connectors for RabbitMQ. 

Meanwhile, RabbitMQ offers integrations with a few databases (like Riak and PostgreSQL). RabbitMQ also offers plugins that you can use to extend core RabbitMQ functionality. For instance, you can use plugins to add support for more protocols (like OAuth 2.0 and STOMP), and to easily enable monitoring with Prometheus.

Kafka also has the upper hand over RabbitMQ when it comes to native stream processing capabilities. The Kafka Streams library allows you to build real-time stream processing apps with features like joins, aggregations, windowing, and exactly-once processing. In comparison, RabbitMQ doesn’t provide any built-in stream processing features.

Kafka vs. RabbitMQ: use cases

There is some overlap in use cases between Kafka and RabbitMQ. For example, you can use both these solutions for:

  • Low-latency messaging following the pub/sub pattern.
  • Decoupling producers and consumers.
  • Integrating different components and microservices in an event-driven architecture.
  • Event streaming and event sourcing.

However, due to their different architectures and capabilities, there are use cases where Kafka is a better choice than RabbitMQ (and vice versa). 

Kafka is the superior choice in the following scenarios:

  • Stream processing.
  • Large-scale systems that handle high-throughput streaming data with consistently low latencies.
  • Use cases where data integrity is critical, and strong message delivery guarantees are needed (exactly-once semantics and message ordering).

Meanwhile, RabbitMQ is a good choice if you need:

  • Flexible messaging patterns (pub/sub, queues, RPC).
  • Multi-protocol support (e.g., AMQP, STOMP, MQTT).
  • Complex message routing.

Kafka and RabbitMQ total cost of ownership (TCO)

Kafka and RabbitMQ are open source projects, which means you don't have to pay to use the software itself. That being said, using open source Kafka/RabbitMQ in a self-hosted environment is certainly not free of cost. Here are the main categories of expenses you’d have to deal with:

  • Infrastructure costs. Includes the servers, storage, and networking resources required.  
  • Operational costs. Refers to all the costs of maintaining, scaling, monitoring, and optimizing your deployment. 
  • Human resources and manpower. This involves the costs of recruiting and training the required staff (DevOps and data engineers, application developers, system architects, etc.), and paying their salaries.  
  • Downtime costs. While hard to quantify, unexpected cluster failures and service unavailability can lead to reputational damage, reduced customer satisfaction, data loss, missed business opportunities, and lost revenue.
  • Miscellaneous expenses. Additional expenses may be required for security and compliance, auditing purposes, and integrations (e.g., building custom integrations and clients in new languages).

The TCO for self-hosting Kafka and RabbitMQ can differ wildly depending on the specifics of your use case, the number of brokers and clusters, the volume of data, and the size of your team. The total cost of a self-managed Kafka or RabbitMQ deployment can range from (tens of) thousands of $ per year (for small deployments and one engineer on payroll) up to millions of $ per year (for large deployments and teams of engineers and architects)

Some things worth mentioning:

  • Kafka can be more expensive than RabbitMQ when large-scale deployments and workloads are involved. That’s because Kafka is designed for hyper-scale scenarios (thousands of brokers, trillions of messages per day), while RabbitMQ is not optimized to reach the same levels of scalability. The more brokers and messages going through the system, the higher the cost of ownership.
  • In terms of data storage costs, Kafka will likely be more expensive. That’s because Kafka can persist vast volumes of data for long periods of time (even indefinitely). You wouldn’t spend as much on persistence with RabbitMQ queues, which generally store data for shorter periods (usually just long enough to ensure message delivery in case of broker or client failures).
  • With RabbitMQ, you might spend more time and money building integrations with other systems (if relevant to your use case). That’s because RabbitMQ offers a very limited number of ready-made integrations. Meanwhile, Kafka offers numerous ready-made connectors so you can easily integrate it with other systems.
  • If stream processing is relevant to your use case, you will likely have higher costs when using RabbitMQ. This is due to the fact that RabbitMQ doesn’t offer native stream processing capabilities, so you would need to pay for and manage an additional component for this purpose. 

If self-managing Kafka or RabbitMQ isn’t to your taste, you have the option of fully managed deployments. The burden of self-hosting might make managed services more cost-effective, especially if you have a smaller team, you don’t want the headache of managing distributed systems, and faster time to market is important to you. As mentioned earlier in this article, there are various RabbitMQ and Kafka vendors out there (the Kafka ones are more numerous), so you can choose the one with the friendliest pricing model for your specific use case and usage patterns

Conclusion

After reading this article, I hope you better understand the key differences and similarities between Kafka and RabbitMQ, and you can more easily decide which one of them is best suited to your specific needs. If you’re keen to see if another messaging system makes more sense for your use case, check out some of our other blog posts:

If you conclude that Kafka is the right technology for you, I encourage you to try out Quix. A fully managed platform that combines Kafka with serverless stream processing, Quix offers an environment to build, test, and deploy services that derive insights from real-time Kafka pipelines. Quix removes the need for you to deal with the operational complexity of deploying and scaling a Kafka-based stream processing engine, reducing the cost and time required to extract business value from real-time data. Check out the official documentation to learn more about the capabilities of the Quix platform.

What’s a Rich Text element?

The rich text element allows you to create and format headings, paragraphs, blockquotes, images, and video all in one place instead of having to add and format them individually. Just double-click and easily create content.

Static and dynamic content editing

A rich text element can be used with static or dynamic content. For static content, just drop it into any page and begin editing. For dynamic content, add a rich text field to any collection and then connect a rich text element to that field in the settings panel. Voila!

How to customize formatting for each rich text

Headings, paragraphs, blockquotes, figures, images, and figure captions can all be styled after a class is added to the rich text element using the "When inside of" nested selector system.

Related content

Banner image for the article "Understanding Kafka’s auto offset reset configuration: Use cases and pitfalls" published on the Quix blog
Ecosystem

Understanding Kafka’s auto offset reset configuration: Use cases and pitfalls

The auto.offset.reset configuration defines how Kafka consumers should behave when no initial committed offsets are available for the partitions assigned to them. Learn how to work with this configuration and discover its related challenges.
Tim Sawicki
Words by
kafka vs spark logos
Ecosystem

Kafka vs Spark - a comparison of stream processing tools

This comparison specifically focuses on Kafka and Spark's streaming extensions — Kafka Streams and Spark Structured Streaming. Kafka Streams excels in per-record processing with a focus on low latency, while Spark Structured Streaming stands out with its built-in support for complex data processing tasks, including advanced analytics, machine learning and graph processing.
Tun Shwe
Words by
image with aws fargate and lambda logos
Ecosystem

Fargate vs Lambda: a comparison of serverless technologies

The main difference between these two serverless compute platforms is that AWS Fargate takes care of the underlying VMs, networking, and other resources you need to run containers using ECS or EKS, whereas AWS Lambda lets you run standalone, stateless functions without having to consider any of the infrastructure whatsoever.
Mike Rosam
Words by