Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 41

YAML Schema for Rate Limit Rules

Recommended resources

Hello! Welcome back to our project.

In our last few lessons, we've built and, most importantly, tested the core distributed algorithms for our rate limiter. You now have robust, interchangeable RateLimiter strategies for Token Bucket and Sliding Window, all backed by Redis and validated with Testcontainers. This is a fantastic achievement and forms the technical heart of our engine.

However, a powerful engine is useless without a steering wheel. Currently, to change a limit from 100 requests per minute to 200, or to switch a specific endpoint from a token bucket to a sliding window, you'd have to change the Java code and redeploy the application. This isn't practical for a real-world, production system.

Your learning outcome for this lesson:

In this lesson, you will learn to design a YAML schema for defining rate limit rules (e.g., algorithm, limits, target keys). This is the first and most critical step in making our rate limiter truly dynamic and configurable, allowing us to manage its behavior entirely through configuration files, without touching the code. This is a cornerstone of your portfolio project's "production-readiness" story.



1. Why Externalize Rate Limit Rules?

The core principle we're introducing is the separation of logic and configuration. Our Java code contains the logic (the algorithms), while an external file (application.yml) will contain the configuration (the rules).

A request from a client can be identified by many different factors, or "dimensions." We need a way to apply different rules based on these dimensions.

Tenant-Based Rate Limiting in Multi-Tenant SaaS
This diagram illustrates how a system can apply different rate limits based on various request attributes like tenant, user, API endpoint, and service tier. Our goal is to create a configuration schema that can express these kinds of complex rules.

To understand the scope of what we might want to configure, it's helpful to see what dimensions are used in real-world systems.

Rate Limiting in AI Gateway

The article 'Rate Limiting in AI Gateway : The Ultimate Guide' discusses the various dimensions on which rate limits can be applied in modern systems. This will help you think about what our schema needs to support.

Read the section titled 'Rate Limiting Options in AI Gateway'. Pay attention to the different dimensions listed, such as 'By API Key or User', 'By Model Type', and 'By Request Type'.

Based on that reading and your project goals, we need to design a schema that can express rules based on:

  • Client IP address
  • API Key (from a header)
  • User ID (from a security context)
  • Request Endpoint (the URL path)
  • Or even a combination of these!

This flexibility is what will make your rate limiter powerful.



2. Designing the YAML Schema for Rules

Our goal is to create a structure in application.yml that is both human-readable and machine-parseable. A common and effective pattern is to define a list of rules. When a request comes in, our system will iterate through this list and apply the first rule that matches the request. This makes the order of rules significant—more specific rules should come before more general ones.

Let's design our schema. We'll put all our configuration under a top-level rate-limiter prefix to keep things organized. The core of this will be a list named rules.

Each rule in the list will be an object with three main parts:

  1. id: A unique name for the rule, which is invaluable for logging and metrics.
  2. match: An object defining the conditions for this rule to apply. This connects directly to the RateLimitKeyResolver strategies you designed earlier.
  3. algorithm and params: Specifies which rate-limiting algorithm to use and provides its specific parameters.

Here is a conceptual example of the schema we will build:

rate-limiter:



  # A flag to enable or disable the rate limiter globally
  enabled: true



  # The list of rules, evaluated in order
  rules:



    # Rule 1: A generous limit for premium users on a specific endpoint
    - id: "premium-user-data-endpoint"
      match:



        # This rule matches if the User ID starts with 'premium-' AND the path matches
        userId: "premium-*" 
        path: "/api/data/**"
      algorithm: TOKEN_BUCKET
      params:
        capacity: 100
        refill-rate: 10
        time-unit: SECONDS




    # Rule 2: A stricter limit for all other requests to the same endpoint
    - id: "default-user-data-endpoint"
      match:
        path: "/api/data/**" # Matches any user on this path
      algorithm: SLIDING_WINDOW
      params:
        limit: 20
        window-size: 60
        time-unit: SECONDS




    # Rule 3: A global fallback rule based on IP address
    - id: "global-ip-fallback"
      match:
        ipAddress: "*" # Matches any IP address
      algorithm: FIXED_WINDOW
      params:
        limit: 100
        window-size: 3600
        time-unit: SECONDS

Notice how this structure is:

  • Ordered: The premium user rule is first. A request from premium-user-123 to /api/data/files will match this rule and stop processing. A request from standard-user-456 will fail the first rule's userId match and fall through to the second rule.
  • Flexible: We can match on different keys (userId, path, ipAddress).
  • Specific: The parameters (params) are nested, allowing each algorithm to have its own unique set of configuration keys (capacity for Token Bucket, limit for Sliding Window).

This is a common pattern in production systems. The following article provides a very similar example within the context of a Spring Cloud Gateway.

Pluggable Rate Limiter Microservice with Spring Cloud Gateway

The article 'Pluggable Rate Limiter Microservice with Spring Cloud Gateway' shows a practical YAML configuration for defining rate limit rules. This will reinforce the structure we've just designed.

Read the section '3.4 Externalized Configuration Management', focusing on the YAML block under 'Spring Cloud Config Server approach'. Compare its structure (rateLimit.routes, algorithm, parameters like replenishRate) to the schema we are designing.

Activity: Write Your Own Rule

Now it's your turn. Based on the schema design above, write a YAML snippet for the following scenario in your own text editor:

Scenario: Limit requests to any endpoint under /api/v1/admin/ to 10 requests per minute. The limit should apply per API key and use the Sliding Window algorithm.

Take a moment to write this rule. A solution is provided below for you to check your work.

Click to see the solution



# Add this rule to the rate-limiter.rules list
- id: "admin-api-key-limit"
  match:
    apiKey: "*" # Match any API key, the key resolver will extract the specific value
    path: "/api/v1/admin/**"
  algorithm: SLIDING_WINDOW
  params:
    limit: 10
    window-size: 1 # Using time-unit: MINUTES
    time-unit: MINUTES

Note: We can use window-size: 60 and time-unit: SECONDS to achieve the same result.



3. The Bridge to Implementation: @ConfigurationProperties

We have a well-designed YAML schema. The next question is: how does our Spring Boot application read this configuration and turn it into usable Java objects?

While we could use the @Value annotation to read each property one by one (@Value("${rate-limiter.rules[0].id}")), this would be incredibly cumbersome, error-prone, and not scalable.

A much better way is to use Spring Boot's @ConfigurationProperties annotation. This powerful feature allows us to bind an entire section of our YAML file to a structured Java class (a POJO, or Plain Old Java Object). It automatically handles nested objects, lists, and even type conversions (e.g., from the string "SECONDS" to a java.util.concurrent.TimeUnit enum).

The video below gives an excellent explanation of why @ConfigurationProperties is the right tool for this job and demonstrates how it works with nested configurations, just like our list of rules.

🛑 Stop Using @Value Everywhere! The Power of @ConfigurationProperties Explained ✅

The video 'Stop Using @Value Everywhere! The Power of @ConfigurationProperties Explained' by CodeSnippet provides a comprehensive guide to this feature. It's the perfect introduction to the topic of our next lesson.

Watch from the beginning until 20:40. Focus on these key concepts: The problems with using @Value for complex configurations. How @ConfigurationProperties with a prefix maps a YAML block to a Java class. Most importantly, pay close attention to how it handles nested classes, lists, and maps, as this is exactly how we will map our rules list.

To give you a concrete idea of where we are heading, the YAML schema we designed would map to Java classes that look something like this (we will implement this in the next lesson):

// The top-level properties class
@ConfigurationProperties(prefix = "rate-limiter")
public class RateLimiterProperties {
    private boolean enabled;
    private List<Rule> rules;
    // getters and setters...
}

// A class representing a single rule
public class Rule {
    private String id;
    private Matcher match;
    private Algorithm algorithm;
    private Map<String, Object> params;
    // getters and setters...
}

// A class for the matching conditions
public class Matcher {
    private String userId;
    private String path;
    private String ipAddress;
    private String apiKey;
    // getters and setters...
}

This clean, type-safe structure is what @ConfigurationProperties enables, and it's the foundation for building our dynamic rule engine.



Conclusion

In this lesson, you took a crucial step from hardcoded logic to a flexible, configuration-driven system. By designing a clear and extensible YAML schema, you've laid the groundwork for a truly professional rate limiter.

Key Takeaways:

  • Externalizing configuration is a best practice that separates your application's logic from its behavior, making it easier to manage in production.
  • A good rate-limiting schema is typically a list of ordered rules, where the first matching rule is applied.
  • Each rule should define what to match (the target key) and what action to take (the algorithm and its parameters).
  • @ConfigurationProperties is the standard Spring Boot mechanism for binding complex YAML structures to type-safe Java objects, and it will be the tool we use to bring our schema to life.

Next Steps

You've designed the blueprint; next, we build the house. In the following lesson, you will bind this YAML configuration to Java objects using Spring's @ConfigurationProperties. We will create the Java classes that mirror our schema and let Spring Boot automatically populate them with the values from your application.yml file, making your rules available within the application.