In today’s cloud-native landscape, many organizations are turning to microservices architectures to build agile, scalable, and maintainable software systems. This article explores how to implement serverless microservices using Azure Functions, Event Grid, Azure Storage, and Cosmos DB. One of the biggest takeaways is that there is no one-size-fits-all approach for persisted storage. Different services can — and should — use the storage or database that best meets their requirements. Another key insight revolves around the cost-effectiveness of Event Grid’s operation-based pricing model, which encourages the creation of well-organized, event-driven microservices with clearly defined boundaries.

This article will examine the opinionated event-driven architecture deployed with the GitHub AT-AT (Automate the Automation with Terraform) Azure Function template. This template provides a scalable pattern for microservices development by allowing each microservice to maintain its own internal storage and database, leveraging different Azure services depending on its needs. The initial focus is on lower-cost serverless services like Blob and Table Storage. This approach is often the natural starting place before considering more expensive, higher-performant or niche-functional scenarios.

Implementing Serverless Microservices with the GitHub AT-AT Azure Function Template The GitHub AT-AT Azure Function template implements a microservices-friendly structure by breaking down responsibilities and allowing each microservice to follow its own logic, storage patterns, and event-publishing requirements. Under the hood, this approach heavily uses Azure Functions for compute, Event Grid for messaging, and multiple storage options to keep data siloed and optimized per microservice.

Why an Opinionated Event-Driven Architecture?

An opinionated event-driven architecture enforces best practices from the get-go. By using Azure Functions, you can create robust, scalable microservices without worrying about provisioning servers. Event Grid is then used to coordinate asynchronous messages (events) across different microservices, eliminating the complexities of tightly coupled systems and instead encouraging a well-defined boundary around each service.

The pricing model for Event Grid is based on operations, not the number of topics. This allows you to create a separate topic for each microservice without incurring heavy costs. As a result, you can delineate your microservices more clearly and easily achieve internal/external event segregation.

Persisted Storage

When implementing microservices, it’s essential to understand that each service can store data according to its own requirements. In this opinionated design, we focus on lower-cost serverless services — specifically, Blob Storage, Table Storage, and the Cosmos DB “SQL API.” These services can be combined or used independently, depending on how you plan to query and manipulate the data.

Table Storage

Table Storage is perfect for small structured entities where BigO notation of N=1 is the norm — meaning there’s no hierarchy or complex structure in the stored object. Each record is effectively an entity with a collection of primitive attributes such as strings, numbers, or booleans.

You can think of this as “spear fishing” for data: you know precisely which record you need, and you retrieve it using a highly specific query that leverages the object’s unique identifier. This single-record lookup approach is cost-effective and sufficiently performant. Although Table Storage may not offer maximum performance in every scenario, it excels in these targeted read scenarios, often providing more than enough speed for typical microservices. If you need higher-performance reads, you may look to more robust database engines or possibly implement a server-side cache with a technology like Redis.

Blob Storage

Blob Storage is generally associated with binary data (like images and video), but it can also be used to store structured data — particularly larger, more complex hierarchical data. When you’re dealing with big JSON documents or similarly rich object graphs and still employing the “spear fishing” approach to data retrieval (accessing data by a narrow primary key), Blob Storage can shine.

The dividing line between Table Storage and Blob Storage for structured data is the complexity of the data model. If it’s small and simple, Table Storage likely suffices. But if it’s a heavier, more complex structure, Blob Storage is a natural fit.

Additionally, the Summary/Details pattern comes into play here. You can store a small summary of your entity in Table Storage and keep the more verbose details in Blob Storage. This allows your microservice to quickly fetch the summary, returning smaller payloads when a client doesn’t need the entire object. Then, if needed, it can fetch the detailed portion from Blob Storage, optimizing performance and storage costs.

Cosmos DB

Sooner or later, you’ll face scenarios that require querying data in more complex ways — like casting a wide net for records that meet certain criteria, or supporting advanced multi-modal queries. Cosmos DB is often an excellent next step because it provides robust horizontal scalability without the complexity and overhead of a more traditional relational database cluster.

Cosmos DB balances strong query capabilities with relatively lower operational friction. Unlike a relational database that might require read replicas, region pairing, and the cumbersome configuration of replication, Cosmos DB simplifies this process, letting you focus on building rather than maintaining. Still, if your performance demands are extremely high in certain transaction-heavy scenarios, a relational database or specialized data store might outperform Cosmos DB. Ultimately, it’s about choosing the right tool for each microservice’s job.

Event-Based Messaging

Microservices typically involve two main communication patterns: synchronous (via REST or other API protocols) and asynchronous (via event publishing and subscription). Event-based messaging is a crucial part of building scalable, decoupled microservices.

Internal Messaging

Within each microservice, asynchronous messaging often facilitates the creation of data aggregates or the fan-out of large workloads into smaller tasks to be processed in parallel. By using a private Event Grid Topic for a specific microservice, you ensure that internal events remain private to that service while public events are only surfaced where needed.

Because Event Grid’s pricing is transaction-based — around $0.60 per million operations — it’s both extremely affordable and flexible. You won’t incur additional costs by spinning up multiple Event Grid Topics. This architecture prevents “noisy neighbor” scenarios by keeping internal events scoped and separated from external consumers.

External Messaging

As your services grow, you’ll likely need to publish events that synchronize data or trigger workflows across multiple microservices. This is where a shared Event Grid Topic can coordinate cross-service activities. Events become the glue for orchestrating complex business processes that span multiple microservices, often referred to as “Sagas.”

The idea behind a Saga is that a single, end-to-end transaction is split across several microservices. Each microservice handles its own smaller, isolated transaction, so you only need to concern yourself with the pre-conditions and post-conditions for that micro-transaction. This is a more manageable approach than trying to replicate a global BEGIN / COMMIT / ROLLBACK TRANSACTION pattern across a distributed system. Sagas can become highly intricate, but thinking about them in micro-transactional increments breaks the complexity down into digestible pieces.

Conclusion

By embracing Azure Functions, Event Grid, and different Azure Storage options, the GitHub AT-AT Azure Function template offers an opinionated yet flexible path to building microservices. The key philosophy is that each microservice owns its data and leverages the storage technology best suited to its access patterns, whether that’s Table Storage for simple entity lookups, Blob Storage for images, videos, and complex hierarchical objects, or Cosmos DB for more advanced querying needs. Event Grid’s cost-effective, operation-based model encourages thoughtful partitioning of events, with each microservice maintaining a private topic for internal tasks and participating in shared topics for cross-service coordination.

When approaching microservices architecture, there is no “one size fits all”. Instead, start with the cost-effective capabilities of Table and Blob Storage, and only add complexity or expense (like Cosmos DB or specialized databases) when your use cases demand it. This approach ensures you pay primarily for what you use, maintain clear boundaries between services, and achieve a scalable, maintainable architecture that can gracefully handle both current requirements and future growth.