Use a gateway to aggregate multiple individual requests into a single request. This pattern is useful when a client must make multiple calls to different backend systems to perform an operation.

Context and problem

In some cases, a client needs to make multiple requests to multiple backend systems. For example, a client may need to retrieve data from multiple services and then aggregate that data into a single response. In this situation, the client must issue multiple requests, which can lead to performance problems. In addition, the client must also handle multiple responses, which can lead to complexity problems.

gateway-aggregation-problem

Solution

Use a gateway to reduce the frequency of communication between clients and services. The gateway receives client requests, dispatches the requests to different backend systems, then aggregates the results and returns them to the requesting client. This pattern can reduce the number of requests an application makes to backend services, and improve application performance over high-latency networks.

gateway-aggregation

Permission aggregation

public interface IPermissionService
{
    Task<IReadOnlyList<PermissionGroupDefinitionResponse>> GetAllPermissionDefinitionsAsync(CancellationToken cancellationToken=default);
}

Refactoring the users and roles controllers

Change UsersController from IdentityServiceDbContext to UserManager<User>

Implement RolesController using RoleManager<Role>

Change UserCreateRequestValidator from IdentityServiceDbContext to UserManager<User>

In the JSON returned by the permission-definition endpoint, rename the name property to groupName

Fixing the remote permission check bug

public async Task<IActionResult> CheckPermission(int roleId, string permissionName, string? resourceType = null, string? resourceId = null)

Considerations

  • The gateway should not introduce service coupling between backend services.

  • The gateway should be located close to the backend services to minimize latency.

  • The gateway service can become a single point of failure. Make sure the gateway is well designed to meet your application’s availability requirements.

  • The gateway can become a bottleneck. Make sure the gateway provides enough performance to handle the load and can scale as your usage grows.

  • Load test the gateway to ensure it doesn’t cause cascading failures in the services.

  • Implement resilient design using techniques such as bulkheads, circuit breaking, retries, and timeouts.

  • If one or more service calls take too long, it may be acceptable to time out and return a partial data set. Consider how your application handles this situation.

  • Use asynchronous I/O to ensure backend latency doesn’t cause performance problems in the application.

  • Implement distributed tracing with correlation IDs to track each call.

  • Monitor request metrics and response sizes.

  • Consider returning cached data (as a failover strategy) to handle failures.

  • Instead of building aggregation into the gateway, consider placing the aggregation service behind the gateway. The resource requirements for request aggregation may differ from those of other services in the gateway, and may affect the gateway’s routing and offloading capabilities.