Self-contained Systems (SCS) in .NET

Self-contained Systems (SCS) in .NET

Introduction

Self-contained Systems (SCS) is an architectural paradigm that has evolved as a response to the challenges of modern software complexity. SCS combines the best characteristics of monolithic and microservices architectures while minimizing their drawbacks.

Philosophical Foundations of SCS

The concept of SCS is based on several fundamental principles:

Autonomy: Each SCS is an independent, self-sufficient unit capable of functioning with minimal dependency on other systems.

Business-Oriented: SCSs reflect real business domains rather than technical abstractions, ensuring a close relationship between business requirements and technical implementation.

Loosely Coupled: Interaction between SCSs is minimal and well-defined, reducing system interdependencies.

Technological Diversity: Each SCS can use a different technology stack, providing opportunities for innovation and optimization.

Independent UI: SCSs often include UI components, enabling end-to-end functionality.

SCS vs Microservices: Nuanced Differences

Although superficially similar, SCS and microservices differ significantly:

Scale and Granularity:

SCS: Larger, encompassing an entire business domain.

Microservices: Smaller, focused on a specific functionality.

UI Integration:

SCS: Often includes its UI.

Microservices: Usually do not include UI, focusing on backend functionality.

Degree of Independence:

SCS: Highly autonomous, with minimal external dependencies.

Microservices: Often tightly integrated with other services.

Data Management:

- SCS: Typically owns a complete database for the domain.

- Microservices: Tends toward data fragmentation.

Deployment Strategy:

- SCS: Can be deployed as a whole system.

- Microservices: Often require more complex orchestration.


Architectural Principles of SCS in the Context of .NET

The .NET ecosystem offers rich tools and frameworks for implementing SCS. Let’s examine the main principles and their realization in .NET.

Ensuring Autonomy

Autonomy is the cornerstone of SCS. In .NET, this can be achieved by:

Isolated Runtime:

- Using .NET self-contained deployment, ensuring the application runs with all necessary dependencies.

- Using Docker for containerization, providing full isolation.

Configuration Isolation:

- Utilizing .NET’s configuration system (`IConfiguration`) individually for each SCS.

- Using Azure App Configuration or AWS AppConfig for centralized but isolated configuration.

Resource Isolation:

- Employing Azure Resource Groups or AWS Resource Groups to isolate resources for each SCS.

Example: Dockerfile for an SCS

FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["OrderingSCS.csproj", "./"]
RUN dotnet restore "OrderingSCS.csproj"
COPY . .
RUN dotnet build "OrderingSCS.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "OrderingSCS.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "OrderingSCS.dll"]
        

API-First Approach and Communication

SCSs interact via well-defined APIs. .NET provides multiple options for implementing this:

RESTful APIs:

- Use ASP.NET Core Web API to create RESTful endpoints.

- Implement API versioning with the Microsoft.AspNetCore.Mvc.Versioning package.

gRPC:

- Leverage .NET's gRPC support for high-performance RPC communication.

- Define contracts using Protobuf.

GraphQL:

- Use the Hot Chocolate framework to create flexible APIs.

- Implement GraphQL schemas and resolvers.

Asynchronous Communication:

- Use Azure Service Bus or RabbitMQ for messaging.

- Integrate MassTransit for message abstraction.

Example: gRPC Service in SCS

public class OrderService : OrderServiceBase
{
    private readonly IOrderRepository _repository;
    private readonly ILogger<OrderService> _logger;
    private readonly IMapper _mapper;

    public OrderService(IOrderRepository repository, ILogger<OrderService> logger, IMapper mapper)
    {
        _repository = repository;
        _logger = logger;
        _mapper = mapper;
    }

    public override async Task<CreateOrderResponse> CreateOrder(CreateOrderRequest request, ServerCallContext context)
    {
        _logger.LogInformation("Creating order for customer {CustomerId}", request.CustomerId);

        var order = _mapper.Map<Order>(request);
        var createdOrder = await _repository.CreateAsync(order);

        var response = _mapper.Map<CreateOrderResponse>(createdOrder);
        
        // Publish an event
        await PublishOrderCreatedEventAsync(createdOrder);

        return response;
    }

    private async Task PublishOrderCreatedEventAsync(Order order)
    {
        // Implementation to publish the event
    }
}
        

Encapsulation of Business Logic

A major advantage of SCS is the clear encapsulation of business logic. In .NET, this can be achieved through:

Domain-Driven Design (DDD):

- Implementing Aggregate Roots, Value Objects, and Domain Services.

- Encapsulating business rules in domain models.

CQRS (Command Query Responsibility Segregation):

- Using MediatR to separate commands and queries.

- Separating read and write models.

Specification Pattern:

- Encapsulating business rules in Specification objects.

- Using the Ardalis.Specification library.

Example: Aggregate Root Using DDD Principles

public class Order : AggregateRoot
{
    private readonly List<OrderItem> _orderItems = new List<OrderItem>();
    public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();

    public CustomerId CustomerId { get; private set; }
    public Money TotalAmount { get; private set; }
    public OrderStatus Status { get; private set; }

    private Order() { } // For EF Core

    public static Order Create(CustomerId customerId)
    {
        var order = new Order
        {
            Id = OrderId.New(),
            CustomerId = customerId,
            Status = OrderStatus.Created,
            TotalAmount = Money.Zero(CurrencyCode.USD)
        };

        order.AddDomainEvent(new OrderCreatedDomainEvent(order));
        return order;
    }

    public void AddOrderItem(ProductId productId, int quantity, Money unitPrice)
    {
        if (Status != OrderStatus.Created)
        {
            throw new OrderDomainException("Cannot add items to non-draft orders");
        }

        var existingItem = _orderItems.FirstOrDefault(i => i.ProductId == productId);
        if (existingItem != null)
        {
            existingItem.IncreaseQuantity(quantity);
        }
        else
        {
            var newItem = new OrderItem(productId, quantity, unitPrice);
            _orderItems.Add(newItem);
        }

        RecalculateTotalAmount();
        AddDomainEvent(new OrderItemAddedDomainEvent(this, productId, quantity));
    }

    private void RecalculateTotalAmount()
    {
        TotalAmount = _orderItems.Aggregate(
            Money.Zero(CurrencyCode.USD),
            (total, item) => total + item.TotalPrice);
    }

    public void ConfirmOrder()
    {
        if (Status != OrderStatus.Created)
        {
            throw new OrderDomainException("Only draft orders can be confirmed");
        }

        if (!_orderItems.Any())
        {
            throw new OrderDomainException("Cannot confirm an empty order");
        }

        Status = OrderStatus.Confirmed;
        AddDomainEvent(new OrderConfirmedDomainEvent(this));
    }
}
        

This example demonstrates how business logic can be encapsulated within an Aggregate Root using DDD principles. This approach ensures centralized management of business rules and maintains domain integrity.

Continued Sections

Asynchronous Communication and Event-Driven Architecture in .NET

  • Practical Implementation of SCS in .NET: Project structure, Dependency Injection, etc.
  • Integration and Communication of SCS: API Gateway, Event-driven communication, etc.
  • Security of SCS: Authentication, Authorization, and Data Encryption.
  • Monitoring and Diagnostics of SCS: Distributed tracing, Metrics collection.
  • Scaling SCS: Horizontal and vertical scaling with Kubernetes and Azure.
  • Migration and Versioning of SCS: Migrating from Monolithic to SCS, API versioning.
  • Performance Optimization of SCS: Caching, Asynchronous operations.
  • Resilience and Disaster Recovery of SCS: Circuit Breaker pattern, Backup strategies.


Conclusion

Self-contained Systems (SCS) architecture is a powerful approach for building complex systems, especially in the .NET ecosystem. It offers a balance between modularity, scalability, and flexibility.

In this article, we have explored the fundamental principles of SCS, their practical implementation in .

NET, and analyzed critical aspects such as integration, security, monitoring, scaling, and performance optimization. Successful adoption of SCS architecture requires the team's learning of new approaches and technologies, but in the long run, it provides significant advantages in terms of system maintainability, development speed, and scalability.

When adopting SCS, it is crucial to pay attention to proper identification of business domains, optimization of communication between systems, and ensuring the autonomy of each SCS.

要查看或添加评论,请登录

社区洞察

其他会员也浏览了