API methodology¶
In my career, I've come across so many approaches to methodology, naming, service versioning and more that I've found my own approach. In this document I will try to describe how APIs should be designed and implemented.
Naming conventions¶
In this section, we will outline the naming conventions for both REST and SOAP services. Proper naming conventions are crucial for maintaining consistency and clarity across your API services.
REST Services¶
- Format: kebab-case
- Convention: {AUTHOR_SERVICE}-{SERVICE_NAME}
- Details: The service name is derived from the type of service. In the context of consumption, the author's name should not be important. This allows for backend replacement without requiring changes from consuming systems. However, if there are two sources for one entity (e.g., CBL.CustomerProfile and SAP CDP), it is advisable to include the source system abbreviation in the service name.
- Example:
customer-profile
,cbl-credit-score
SOAP Services¶
- Format: PascalCase
- Convention: {AUTHOR_SERVICE}{SERVICE_NAME}
- Details: SOAP services usually include the backend application name. If not, the same approach as for REST services is followed.
- Example:
CBLCustomerProfile
,SAPCDPCustomerProfile
API design¶
A well-designed API should be rooted in a domain model that accurately represents the business logic and real-world entities it’s intended to manage. By defining attributes based on the domain model, we ensure that the API aligns with the language and concepts understood by both developers and business stakeholders. This is crucial for maintaining a shared vocabulary across the development process, which helps to avoid confusion and inconsistencies. For example, if we have clientId and clientIdentification as two separate attributes, it would be more effective to unify them under a single, semantically correct label, as they represent the same concept. This eliminates redundancy and prevents ambiguity. The API should be readable and understandable not only by developers but also by business analysts, so it's essential to ensure that everyone involved in the design process has a clear understanding of the meaning of each attribute. A good practice is to sit down with both the development team and business analysts to define and validate the domain model, ensuring that the attributes and terms used are meaningful, consistent, and aligned with the business requirements.
Additionally, when designing URLs for your API, it’s important to use a consistent and logical structure. URLs should reflect the resources being accessed and follow a clear naming convention. For instance, use plural nouns for resource names (e.g., /users, /products) to maintain a predictable and easily understandable structure. Another key point is using camelCase for attribute names (e.g., clientId, userAddress) to ensure consistency and readability, which is my preferred naming convention. This approach reduces confusion and maintains uniformity across the API. Furthermore, the API should be versioned from the outset to allow for backward compatibility when new features are added or changes are made to the existing structure. URLs should include version numbers (e.g., /v1/users), making it easier to manage changes over time without breaking existing functionality.
API versioning¶
I have come across many different approaches to versioning APIs. In this section, I will outline the different approaches
URL versioning¶
The most common approach to versioning is to include the version in the URL. This is the most straightforward approach.
The versioning convention uses a prefix "v" followed by the version number, for example, v1
. Versions are
incremented
when the API is not backward compatible, and the producer can support both the old and new versions. The producer must
always notify consumers when a new version is released. Consumers must be designed to handle the addition of new
attributes in the service response without causing processing failures. New attributes should be ignored if not needed.
For example, version 1 would be denoted as v1
, and version 2 as v2
if it is not backward compatible. By
following these
conventions, you can ensure that your API services remain robust and adaptable to changes.
Header versioning¶
A less common but still very useful approach is to specify the API version in the request header. This approach is
suitable for scenarios where you want to keep the URL clean and avoid versioning in the endpoint path. By including the
version in the header, you can manage versions more flexibly and independently of the URL structure. This method also
allows for easier version negotiation between the client and server. For example, you can include version in accept
header like
header: application/pastyrik.dev.v2+json
in your requests to specify the desired API version. This approach ensures
that the versioning logic is
abstracted away from the URL, providing a cleaner and more maintainable API design.
API documentation¶
I'm not a fan of various Word documents, Excel sheets, interface agreements, and other files produced by architects who expect programmers to implement everything exactly as written. For me, the only valid API documentation is an OpenAPI definition or WSDL, which clearly describes individual attributes, API behavior, and execution options.
As a Java developer, I’m also not a proponent of the "documentation first" approach, meaning generating code from API documentation. Yes, having such a definition can be useful for providing a general idea and sharing it with developers, but generating code from it is a bad practice. I've encountered multiple cases where a public service had an OpenAPI definition that was invalid and caused errors. A prime example is Czech BankID, where I had to write my own corrected OpenAPI definition to properly generate objects. In this case, the original definition was not only invalid but also poorly structured— instead of simply defining a full string field, they used a labeled string with values separated by commas, making things unnecessarily complicated.
For me, the only valid API definition is the one generated directly from the code. This way, we can see exactly how the API is structured and implemented. Additionally, if we have SwaggerUI running on the application, we can always check the live API documentation in each environment and verify what is currently deployed.
If an API is part of a more complex orchestration service, I’ve noticed a positive trend where even architects are using Git to publish all sequencing and activity diagrams. They often use tools like PlantUML or Mermaid, which makes it easier to version and track changes. This also allows developers to directly reference a specific page in Git from the OpenAPI definition, ensuring that documentation remains consistent and up to date.
Security¶
Service security is a critical topic in today’s increasingly hostile digital environment. One key aspect is ensuring the ability to identify who triggered a particular event. For example, in banking transactions, it’s essential to distinguish whether the transaction was initiated by a bank employee at a branch, the account owner via online banking, or through a mobile app.
The most commonly used authentication mechanisms for this purpose are OAuth 2.0 and JWT tokens. These tokens typically contain the identity of the client, the application in which the user is authenticated, and relevant roles and scopes. API management solutions use these tokens to verify who the caller is and whether the application has the necessary permissions to access services exposed through the API gateway.
Once the request reaches the backend, additional validation is performed to ensure that the client—whether a user or an application in machine-to-machine communication—has the required roles and scopes to execute the requested operation. Proper implementation of these security controls helps prevent unauthorized access and ensures that API interactions follow the principle of least privilege.
Beyond authentication and authorization, securing APIs also involves enforcing best practices such as rate limiting, IP whitelisting, and anomaly detection to prevent abuse. Transport security (e.g., TLS) is a given, but data encryption at rest and in transit, along with detailed logging and monitoring, are also crucial for identifying and mitigating security threats in real time.
Terminology¶
Authentication is the process of verifying the identity of a user or system. It ensures that the entity is who it claims to be. Common methods include passwords, biometrics, and tokens.
Authorization is the process of determining what an authenticated user or system is allowed to do. It defines permissions and access levels for resources and actions.
In summary:
- Authentication: Who are you?
- Authorization: What are you allowed to do?
Error handling¶
As a developer, I must ensure that technical errors are never exposed to end users in the event of a system failure. Detailed error messages can reveal sensitive implementation details, which could be exploited by attackers. Instead, users should receive only generic error messages, such as "An unexpected error occurred. Please try again later."
However, proper logging is crucial to identifying and resolving issues efficiently. Every error must be logged with sufficient detail so that I can trace the root cause of the problem. Logs should include contextual information such as request parameters (excluding sensitive data), timestamps, user identifiers (if applicable), and relevant stack traces.
If the error is caused by incorrect input from the user, I should provide a clear, actionable message. For example, instead of saying "400 Bad Request", I should inform the user precisely what went wrong: "The provided email address is not valid." This improves the user experience while maintaining security.
From a broader perspective, proper error handling should follow best practices such as:
- Using standard HTTP status codes (e.g., 400 for bad input, 401 for unauthorized access, 404 for missing resources, 500 for internal errors).
- Logging errors with appropriate severity levels (e.g., INFO for expected issues, ERROR for critical failures).
- Implementing structured logging for better analysis and monitoring.
- Avoiding stack trace exposure in API responses.
- Integrating with monitoring tools to detect patterns and prevent recurring issues.
By balancing security, usability, and diagnostics, I can ensure that errors are handled properly without compromising the system's integrity or the user's experience.
Example of a good error response:
{
"error": "Internal Server Error",
"message": "An unexpected error occurred. Please try again later.",
"code": 500
}
Rate Limiting¶
Rate limiting is a crucial aspect of API security and performance management. It helps prevent abuse by restricting the number of requests a client can make within a specified time period. This ensures fair usage, protects the API from being overwhelmed by excessive requests, and mitigates brute force or denial-of-service (DoS) attacks.
Implementation Strategies¶
Rate limiting can be enforced using various strategies, depending on the use case:
- Fixed Window: Limits the number of requests within a fixed time window (e.g., 100 requests per minute). Once the limit is reached, all further requests are blocked until the next window starts.
- Sliding Window: Similar to the fixed window approach but allows more granular control by dynamically adjusting the window based on request timestamps.
- Token Bucket: A bucket is filled with tokens at a fixed rate, and each request consumes a token. If the bucket is empty, requests are delayed or rejected. This method allows for short bursts of requests while maintaining an average rate.
- Leaky Bucket: Works like the token bucket but ensures a steady outflow of requests, preventing sudden bursts while smoothing out traffic.
Handling Rate Limits¶
To ensure proper communication with API consumers, the API should:
- Use standard HTTP response codes:
429 Too Many Requests
– When the rate limit is exceeded.
-
Return appropriate headers to inform clients about rate limits:
-
Differentiate by client type: Some APIs enforce stricter limits on anonymous users while providing higher limits for authenticated users or premium customers.
Integration with API Gateways¶
Most API gateways (e.g., Kong, Apigee, AWS API Gateway, Nginx) provide built-in rate limiting features that can be configured per API, client, or IP address. Proper logging and monitoring should also be in place to detect unusual patterns and dynamically adjust limits when needed.
By implementing an effective rate-limiting strategy, APIs remain performant, secure, and resistant to abuse while ensuring a fair experience for all users.