Skip to content

REST API in ConnectSoft Microservice Template

Purpose & Overview

The REST API implementation in the ConnectSoft Microservice Template provides a standardized HTTP-based interface for accessing microservice functionality. It follows RESTful principles and integrates seamlessly with the template's Clean Architecture, CQRS pattern, and domain-driven design approach.

Why REST API?

REST API offers several benefits for microservices:

  • Universal Compatibility: Works with any HTTP client, including web browsers, mobile apps, and other services
  • Statelessness: Each request contains all information needed to process it, enabling scalability
  • Cacheability: HTTP caching can significantly improve performance
  • Standard HTTP Methods: Leverages well-understood HTTP verbs (GET, POST, PUT, DELETE, PATCH)
  • Discoverability: OpenAPI/Swagger documentation provides self-documenting APIs
  • Platform Independence: Works across different programming languages and platforms

Architecture Overview

REST API sits at the outermost layer of the Clean Architecture:

HTTP Client
REST API Controllers (ServiceModel.RestApi)
    ├── Request DTOs (ServiceModel)
    ├── Response DTOs (ServiceModel)
    └── AutoMapper (Request/Response ↔ Domain Input/Output)
Domain Model (DomainModel)
    ├── Processors (Commands/Writes)
    └── Retrievers (Queries/Reads)
Repository Layer (PersistenceModel)
    └── Data Store

Core Components

1. Controllers

Controllers handle HTTP requests and delegate to domain services:

[ApiController]
[Route("api/[controller]")]
public class MicroserviceAggregateRootsServiceController(
    ILogger<MicroserviceAggregateRootsServiceController> logger,
    IMicroserviceAggregateRootsRetriever retriever,
    IMicroserviceAggregateRootsProcessor processor,
    IMapper mapper)
    : ControllerBase, IMicroserviceAggregateRootQueryService, IMicroserviceAggregateRootProcessService
{
    private readonly ILogger<MicroserviceAggregateRootsServiceController> logger = logger;
    private readonly IMicroserviceAggregateRootsRetriever retriever = retriever;
    private readonly IMicroserviceAggregateRootsProcessor processor = processor;
    private readonly IMapper mapper = mapper;

    /// <summary>
    /// Process, create and store a MicroserviceAggregateRoots.
    /// </summary>
    [HttpPost("MicroserviceAggregateRoots/", Name = nameof(CreateMicroserviceAggregateRoot))]
    [SwaggerOperation(
        Summary = "Process, create and store a MicroserviceAggregateRoots.",
        Description = "Process, create and store a MicroserviceAggregateRoots.",
        OperationId = nameof(CreateMicroserviceAggregateRoot),
        Tags = ["MicroserviceAggregateRoots"])]
    [SwaggerResponse(StatusCodes.Status200OK, "Created MicroserviceAggregateRoot.", typeof(CreateMicroserviceAggregateRootResponse), MediaTypeNames.Application.Json)]
    [SwaggerResponse(StatusCodes.Status400BadRequest, "The request is invalid.", typeof(ValidationProblemDetails), MediaTypeNames.Application.ProblemJson)]
    [SwaggerResponse(StatusCodes.Status500InternalServerError, "An un-handled exception occurred.", typeof(ProblemDetails), MediaTypeNames.Application.ProblemJson)]
    public async Task<CreateMicroserviceAggregateRootResponse> CreateMicroserviceAggregateRoot(
        [FromBody, SwaggerRequestBody("Create MicroserviceAggregateRoot request payload", Required = true)] 
        CreateMicroserviceAggregateRootRequest request, 
        CancellationToken token = default)
    {
        Guid objectId = request.ObjectId;
        this.logger.Here(log => log.LogInformation("Create MicroserviceAggregateRoot for {ObjectId} started...", objectId));

        CreateMicroserviceAggregateRootInput input = this.mapper.Map<CreateMicroserviceAggregateRootRequest, CreateMicroserviceAggregateRootInput>(request);

        IMicroserviceAggregateRoot foundEntity = await this.processor.CreateMicroserviceAggregateRoot(input, token).ConfigureAwait(false);

        CreateMicroserviceAggregateRootResponse response = null;
        if (foundEntity != null)
        {
            response = new CreateMicroserviceAggregateRootResponse
            {
                CreatedMicroserviceAggregateRoot = this.mapper.Map<IMicroserviceAggregateRoot, MicroserviceAggregateRootDto>(foundEntity),
            };
        }

        this.logger.Here(log => log.LogInformation("Create MicroserviceAggregateRoot for {ObjectId} finished...", objectId));

        return await Task.FromResult(response).ConfigureAwait(false);
    }

    /// <summary>
    /// Gets MicroserviceAggregateRoot details.
    /// </summary>
    [HttpGet("MicroserviceAggregateRoots/", Name = nameof(GetMicroserviceAggregateRootDetails))]
    [SwaggerOperation(
        Summary = "Gets MicroserviceAggregateRoot details.",
        Description = "Gets MicroserviceAggregateRoot details.",
        OperationId = nameof(GetMicroserviceAggregateRootDetails),
        Tags = ["MicroserviceAggregateRoots"])]
    [SwaggerResponse(StatusCodes.Status200OK, "Gets MicroserviceAggregateRoot details.", typeof(GetMicroserviceAggregateRootDetailsResponse), MediaTypeNames.Application.Json)]
    [SwaggerResponse(StatusCodes.Status404NotFound, "A MicroserviceAggregateRoot with the specified identifier not found.", typeof(ProblemDetails), MediaTypeNames.Application.ProblemJson)]
    public async Task<GetMicroserviceAggregateRootDetailsResponse> GetMicroserviceAggregateRootDetails(
        [FromQuery] GetMicroserviceAggregateRootDetailsRequest request, 
        CancellationToken token = default)
    {
        Guid objectId = request.ObjectId;

        this.logger.Here(log => log.LogInformation("Get MicroserviceAggregateRoot details for {ObjectId} started...", objectId));

        GetMicroserviceAggregateRootDetailsInput input = this.mapper.Map<GetMicroserviceAggregateRootDetailsRequest, GetMicroserviceAggregateRootDetailsInput>(request);

        IMicroserviceAggregateRoot foundEntity = await this.retriever.GetMicroserviceAggregateRootDetails(input, token).ConfigureAwait(false);

        GetMicroserviceAggregateRootDetailsResponse response = null;
        if (foundEntity != null)
        {
            response = new GetMicroserviceAggregateRootDetailsResponse
            {
                FoundMicroserviceAggregateRoot = this.mapper.Map<IMicroserviceAggregateRoot, MicroserviceAggregateRootDto>(foundEntity),
            };
        }

        this.logger.Here(log => log.LogInformation("Get MicroserviceAggregateRoot details for {ObjectId} finished...", objectId));

        return await Task.FromResult(response).ConfigureAwait(false);
    }

    /// <summary>
    /// Delete a given MicroserviceAggregateRoot.
    /// </summary>
    [HttpDelete("MicroserviceAggregateRoots/", Name = nameof(DeleteMicroserviceAggregateRoot))]
    [SwaggerOperation(
        Summary = "Delete a given MicroserviceAggregateRoot.",
        Description = "Delete a given MicroserviceAggregateRoot.",
        OperationId = nameof(DeleteMicroserviceAggregateRoot),
        Tags = ["MicroserviceAggregateRoots"])]
    [SwaggerResponse(StatusCodes.Status200OK, "The MicroserviceAggregateRoot successfully deleted.")]
    [SwaggerResponse(StatusCodes.Status404NotFound, "A MicroserviceAggregateRoot with the specified identifier not found.", typeof(ProblemDetails), MediaTypeNames.Application.ProblemJson)]
    public async Task DeleteMicroserviceAggregateRoot(
        [FromBody, SwaggerRequestBody("Delete MicroserviceAggregateRoot request payload", Required = true)] 
        DeleteMicroserviceAggregateRootRequest request,
        CancellationToken token = default)
    {
        Guid objectId = request.ObjectId;
        this.logger.Here(log => log.LogInformation("Delete MicroserviceAggregateRoot for {ObjectId} started...", objectId));

        DeleteMicroserviceAggregateRootInput input = this.mapper.Map<DeleteMicroserviceAggregateRootRequest, DeleteMicroserviceAggregateRootInput>(request);

        await this.processor.DeleteMicroserviceAggregateRoot(input, token).ConfigureAwait(false);

        this.logger.Here(log => log.LogInformation("Delete MicroserviceAggregateRoot for {ObjectId} finished...", objectId));

        await Task.CompletedTask.ConfigureAwait(false);
    }
}

2. Request/Response DTOs

Request and Response objects are defined in the ServiceModel project:

// Request DTO
public class CreateMicroserviceAggregateRootRequest
{
    [Required]
    [NotDefault]
    public Guid ObjectId { get; set; }

    // Additional request properties
}

// Response DTO
public class CreateMicroserviceAggregateRootResponse
{
    public MicroserviceAggregateRootDto CreatedMicroserviceAggregateRoot { get; set; }
}

3. HTTP Status Codes

The template uses standard HTTP status codes:

Status Code Meaning Usage
200 OK Success Successful GET, PUT, DELETE, PATCH
201 Created Resource created Successful POST that creates a resource
400 Bad Request Invalid request Validation errors, malformed request
401 Unauthorized Authentication required Missing or invalid authentication
403 Forbidden Access denied Authenticated but not authorized
404 Not Found Resource not found Requested resource doesn't exist
409 Conflict Conflict Resource conflict (e.g., duplicate)
422 Unprocessable Entity Business logic error Valid syntax but business rule violation
500 Internal Server Error Server error Unexpected server error
502 Bad Gateway Gateway error Invalid response from upstream
503 Service Unavailable Service unavailable Service temporarily unavailable
504 Gateway Timeout Timeout Upstream service timeout

4. Swagger/OpenAPI Integration

The template includes comprehensive Swagger/OpenAPI documentation:

// SwaggerExtensions.cs
internal static IServiceCollection AddSwagger(this IServiceCollection services)
{
    if (OptionsExtensions.SwaggerOptions.EnableSwagger)
    {
        string serviceModelXmlCommentsFilePath = null, apiModelXmlCommentsFilePath = null;

        // Find XML documentation files
        string[] files = Directory.GetFiles(
            AppDomain.CurrentDomain.BaseDirectory, 
            searchPattern: "ConnectSoft.MicroserviceTemplate.ServiceModel.xml", 
            searchOption: SearchOption.AllDirectories);
        if (files != null && files.Length == 1)
        {
            serviceModelXmlCommentsFilePath = files[0];
        }

        files = Directory.GetFiles(
            AppDomain.CurrentDomain.BaseDirectory, 
            searchPattern: "ConnectSoft.MicroserviceTemplate.ServiceModel.RestApi.xml", 
            searchOption: SearchOption.AllDirectories);
        if (files != null && files.Length == 1)
        {
            apiModelXmlCommentsFilePath = files[0];
        }

        services.AddSwaggerGen(c =>
        {
            c.SwaggerDoc("v1", new OpenApiInfo 
            { 
                Title = "ConnectSoft.MicroserviceTemplate's microservice back-end API", 
                Version = "v1" 
            });

            if (!string.IsNullOrEmpty(serviceModelXmlCommentsFilePath))
            {
                c.IncludeXmlComments(serviceModelXmlCommentsFilePath);
            }

            if (!string.IsNullOrEmpty(apiModelXmlCommentsFilePath))
            {
                c.IncludeXmlComments(apiModelXmlCommentsFilePath);
            }

            c.EnableAnnotations(enableAnnotationsForInheritance: true, enableAnnotationsForPolymorphism: true);
            c.UseAllOfForInheritance();
        });
    }

    return services;
}

internal static IApplicationBuilder UseMicroserviceSwagger(this IApplicationBuilder application)
{
    SwaggerOptions swaggerOptions = application.ApplicationServices
        .GetRequiredService<IOptions<SwaggerOptions>>().Value;

    if (swaggerOptions.EnableSwagger)
    {
        // Enable middleware to serve generated Swagger as a JSON endpoint
        application.UseSwagger();

        if (swaggerOptions.EnableSwaggerUI)
        {
            // Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.)
            application.UseSwaggerUI(options =>
            {
                options.DocumentTitle = "ConnectSoft.MicroserviceTemplate's microservice back-end API";

                if (!string.IsNullOrEmpty(swaggerOptions.SwaggerUITheme))
                {
                    options.InjectStylesheet(swaggerOptions.SwaggerUITheme);
                }

                options.SwaggerEndpoint("/swagger/v1/swagger.json", 
                    "ConnectSoft.MicroserviceTemplate's microservice back-end API.");

                options.DisplayOperationId();
                options.DisplayRequestDuration();
            });
        }
    }

    return application;
}

5. Controller Registration

Controllers are registered via application parts:

// WebApiExtensions.cs
internal static IServiceCollection AddWebApiCommunication(this IServiceCollection services)
{
    services
        .AddControllers(config =>
        {
            config.ReturnHttpNotAcceptable = true;
            config.RespectBrowserAcceptHeader = true;
        })
        .AddJsonOptions(jsonOptions =>
        {
            jsonOptions.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
        })
        .AddDataAnnotationsLocalization()
        .AddApplicationPart(typeof(MicroserviceAggregateRootsServiceController).Assembly)
        .AddControllersAsServices(); // Register controllers in DI container

    return services;
}

internal static IEndpointRouteBuilder MapMicroserviceControllers(this IEndpointRouteBuilder endpoints)
{
    endpoints.MapControllers();
    return endpoints;
}

REST API Best Practices

Do's

  1. Use appropriate HTTP methods
  2. GET: Retrieve resources (idempotent)
  3. POST: Create resources or perform actions
  4. PUT: Update entire resource (idempotent)
  5. PATCH: Partial update (idempotent)
  6. DELETE: Remove resources (idempotent)

  7. Follow RESTful naming conventions

  8. Use nouns for resources: /api/MicroserviceAggregateRoots
  9. Use plural nouns: /api/Users not /api/User
  10. Avoid verbs in URLs: /api/GetUser ❌ → /api/Users/{id}

  11. Use appropriate status codes

  12. 200 OK for successful GET, PUT, DELETE
  13. 201 Created for successful POST that creates resources
  14. 400 Bad Request for validation errors
  15. 404 Not Found for missing resources
  16. 422 Unprocessable Entity for business logic violations

  17. Provide comprehensive Swagger documentation

  18. Document all endpoints with [SwaggerOperation]
  19. Specify response types with [SwaggerResponse]
  20. Include XML comments for detailed descriptions

  21. Handle errors consistently

  22. Use ValidationProblemDetails for validation errors
  23. Use ProblemDetails for other errors
  24. Include correlation IDs for error tracking

  25. Support async operations

  26. All controller actions should be async
  27. Use CancellationToken for cancellation support
  28. Configure await false when appropriate

  29. Map between layers

  30. Use AutoMapper to map between Request/Response DTOs and Domain Input/Output
  31. Never expose domain entities directly
  32. Keep DTOs focused on API concerns

Don'ts

  1. Don't put business logic in controllers
  2. Controllers should only handle HTTP concerns
  3. Delegate all business logic to domain services

  4. Don't expose domain entities directly

  5. Use DTOs to isolate API from domain model
  6. Prevent internal structure leakage

  7. Don't use GET for mutations

  8. GET requests should be idempotent and side-effect free
  9. Use POST for actions that modify state

  10. Don't ignore HTTP semantics

  11. Respect HTTP method semantics
  12. Use appropriate status codes

  13. Don't mix concerns

  14. Keep controllers thin
  15. Handle HTTP concerns only

Integration with CQRS

REST API integrates with CQRS pattern:

// Command endpoint (write operation)
[HttpPost("MicroserviceAggregateRoots/")]
public async Task<CreateMicroserviceAggregateRootResponse> CreateMicroserviceAggregateRoot(
    [FromBody] CreateMicroserviceAggregateRootRequest request,
    CancellationToken token = default)
{
    // Map request to domain input
    var input = mapper.Map<CreateMicroserviceAggregateRootInput>(request);

    // Use Processor (command side)
    var result = await processor.CreateMicroserviceAggregateRoot(input, token);

    // Map domain output to response
    return mapper.Map<CreateMicroserviceAggregateRootResponse>(result);
}

// Query endpoint (read operation)
[HttpGet("MicroserviceAggregateRoots/")]
public async Task<GetMicroserviceAggregateRootDetailsResponse> GetMicroserviceAggregateRootDetails(
    [FromQuery] GetMicroserviceAggregateRootDetailsRequest request,
    CancellationToken token = default)
{
    // Map request to domain input
    var input = mapper.Map<GetMicroserviceAggregateRootDetailsInput>(request);

    // Use Retriever (query side)
    var result = await retriever.GetMicroserviceAggregateRootDetails(input, token);

    // Map domain output to response
    return mapper.Map<GetMicroserviceAggregateRootDetailsResponse>(result);
}

Error Handling

Validation Errors

Validation errors are automatically handled by ASP.NET Core:

[HttpPost("MicroserviceAggregateRoots/")]
public async Task<CreateMicroserviceAggregateRootResponse> CreateMicroserviceAggregateRoot(
    [FromBody] CreateMicroserviceAggregateRootRequest request,
    CancellationToken token = default)
{
    // If request is invalid, framework automatically returns 400 Bad Request
    // with ValidationProblemDetails containing error details

    // Process valid request...
}

Business Logic Errors

Domain exceptions should be caught and converted to appropriate HTTP responses:

// In exception handling middleware or controller
try
{
    var result = await processor.CreateMicroserviceAggregateRoot(input, token);
    return Ok(result);
}
catch (AggregateNotFoundException ex)
{
    return NotFound(new ProblemDetails
    {
        Title = "Resource not found",
        Detail = ex.Message,
        Status = StatusCodes.Status404NotFound
    });
}
catch (BusinessRuleViolationException ex)
{
    return UnprocessableEntity(new ValidationProblemDetails
    {
        Title = "Business rule violation",
        Detail = ex.Message,
        Status = StatusCodes.Status422UnprocessableEntity
    });
}

Configuration

Swagger Options

Swagger can be configured via SwaggerOptions:

{
  "SwaggerOptions": {
    "EnableSwagger": true,
    "EnableSwaggerUI": true,
    "SwaggerUITheme": "/swagger-ui/themes/3.x/theme-monokai.css"
  }
}

JSON Serialization

JSON options are configured in WebApiExtensions:

.AddJsonOptions(jsonOptions =>
{
    // Support enum as strings
    jsonOptions.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());

    // Additional JSON options can be configured here
})

Common Scenarios

Scenario 1: Simple CRUD Operations

// Create
[HttpPost("Resources/")]
public async Task<CreateResourceResponse> CreateResource([FromBody] CreateResourceRequest request)
{
    var input = mapper.Map<CreateResourceInput>(request);
    var result = await processor.CreateResource(input);
    return mapper.Map<CreateResourceResponse>(result);
}

// Read
[HttpGet("Resources/{id}")]
public async Task<GetResourceResponse> GetResource(Guid id)
{
    var input = new GetResourceInput { Id = id };
    var result = await retriever.GetResource(input);
    return mapper.Map<GetResourceResponse>(result);
}

// Update
[HttpPut("Resources/")]
public async Task<UpdateResourceResponse> UpdateResource([FromBody] UpdateResourceRequest request)
{
    var input = mapper.Map<UpdateResourceInput>(request);
    var result = await processor.UpdateResource(input);
    return mapper.Map<UpdateResourceResponse>(result);
}

// Delete
[HttpDelete("Resources/")]
public async Task DeleteResource([FromBody] DeleteResourceRequest request)
{
    var input = mapper.Map<DeleteResourceInput>(request);
    await processor.DeleteResource(input);
}

Scenario 2: Query with Filters

[HttpGet("Resources/")]
public async Task<SearchResourcesResponse> SearchResources(
    [FromQuery] SearchResourcesRequest request)
{
    var input = mapper.Map<SearchResourcesInput>(request);
    var results = await retriever.SearchResources(input);
    return mapper.Map<SearchResourcesResponse>(results);
}

// Request with query parameters
public class SearchResourcesRequest
{
    public string? Name { get; set; }
    public ResourceStatus? Status { get; set; }
    public DateTimeOffset? CreatedAfter { get; set; }
    public int Page { get; set; } = 1;
    public int PageSize { get; set; } = 20;
}

Scenario 3: Action Endpoints

For operations that don't fit standard CRUD:

[HttpPost("Orders/{id}/cancel")]
public async Task<CancelOrderResponse> CancelOrder(
    Guid id,
    [FromBody] CancelOrderRequest request)
{
    var input = new CancelOrderInput 
    { 
        OrderId = id, 
        Reason = request.Reason 
    };
    var result = await processor.CancelOrder(input);
    return mapper.Map<CancelOrderResponse>(result);
}

Testing REST API

Unit Testing Controllers

[Fact]
public async Task CreateMicroserviceAggregateRoot_Should_Return_Ok()
{
    // Arrange
    var mockProcessor = new Mock<IMicroserviceAggregateRootsProcessor>();
    var mockMapper = new Mock<IMapper>();
    var controller = new MicroserviceAggregateRootsServiceController(
        Mock.Of<ILogger<MicroserviceAggregateRootsServiceController>>(),
        Mock.Of<IMicroserviceAggregateRootsRetriever>(),
        mockProcessor.Object,
        mockMapper.Object);

    var request = new CreateMicroserviceAggregateRootRequest { ObjectId = Guid.NewGuid() };
    var input = new CreateMicroserviceAggregateRootInput { ObjectId = request.ObjectId };
    var entity = Mock.Of<IMicroserviceAggregateRoot>();
    var response = new CreateMicroserviceAggregateRootResponse();

    mockMapper.Setup(m => m.Map<CreateMicroserviceAggregateRootInput>(request))
        .Returns(input);
    mockProcessor.Setup(p => p.CreateMicroserviceAggregateRoot(input, It.IsAny<CancellationToken>()))
        .ReturnsAsync(entity);
    mockMapper.Setup(m => m.Map<CreateMicroserviceAggregateRootResponse>(It.IsAny<object>()))
        .Returns(response);

    // Act
    var result = await controller.CreateMicroserviceAggregateRoot(request);

    // Assert
    Assert.NotNull(result);
    mockProcessor.Verify(p => p.CreateMicroserviceAggregateRoot(input, It.IsAny<CancellationToken>()), Times.Once);
}

Integration Testing

Use WebApplicationFactory for integration tests:

public class MicroserviceAggregateRootsControllerTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly WebApplicationFactory<Program> _factory;
    private readonly HttpClient _client;

    public MicroserviceAggregateRootsControllerTests(WebApplicationFactory<Program> factory)
    {
        _factory = factory;
        _client = _factory.CreateClient();
    }

    [Fact]
    public async Task GetMicroserviceAggregateRoot_Should_Return_Ok()
    {
        // Arrange
        var id = Guid.NewGuid();

        // Act
        var response = await _client.GetAsync($"/api/MicroserviceAggregateRootsService?ObjectId={id}");

        // Assert
        response.EnsureSuccessStatusCode();
        var content = await response.Content.ReadAsStringAsync();
        Assert.NotNull(content);
    }
}

Advanced Topics

API Versioning

API versioning can be implemented using Microsoft.AspNetCore.Mvc.Versioning. See API Versioning for comprehensive versioning strategies, implementation patterns, and best practices across REST API, GraphQL, gRPC, and CoreWCF.

Rate Limiting

Rate limiting can be configured globally or per endpoint:

services.AddRateLimiter(options =>
{
    options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
        RateLimitPartition.GetFixedWindowLimiter(
            partitionKey: context.User.Identity?.Name ?? context.Request.Headers.Host.ToString(),
            factory: partition => new FixedWindowRateLimiterOptions
            {
                AutoReplenishment = true,
                PermitLimit = 100,
                Window = TimeSpan.FromMinutes(1)
            }));
});

CORS Configuration

CORS can be configured for cross-origin requests:

services.AddCors(options =>
{
    options.AddPolicy("AllowSpecificOrigin", policy =>
    {
        policy.WithOrigins("https://example.com")
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

Summary

The REST API implementation in the ConnectSoft Microservice Template provides:

  • Clean Architecture: Controllers in the outermost layer
  • CQRS Integration: Separate endpoints for commands and queries
  • Comprehensive Documentation: Swagger/OpenAPI integration
  • Error Handling: Standard HTTP status codes and error responses
  • Validation: Automatic request validation
  • Async Support: Full async/await support
  • Testability: Easy to unit and integration test

By following these patterns and best practices, you can build robust, scalable, and maintainable REST APIs that integrate seamlessly with the template's architectural patterns.