Back to Abp

ABP Microservice Solution Template

.agents/skills/abp-microservice/SKILL.md

10.3.06.5 KB
Original Source

ABP Microservice Solution Template

Docs: https://abp.io/docs/latest/solution-templates/microservice

Solution Structure

MyMicroservice/
├── apps/                           # UI applications
│   ├── web/                        # Web application
│   ├── public-web/                 # Public website
│   └── auth-server/                # Authentication server (OpenIddict)
├── gateways/                       # BFF pattern - one gateway per UI
│   └── web-gateway/                # YARP reverse proxy
├── services/                       # Microservices
│   ├── administration/             # Permissions, settings, features
│   ├── identity/                   # Users, roles
│   └── [your-services]/            # Your business services
└── etc/
    ├── docker/                     # Docker compose for local infra
    └── helm/                       # Kubernetes deployment

Microservice Structure (NOT Layered!)

Each microservice has simplified structure - everything in one project:

services/ordering/
├── OrderingService/                # Main project
│   ├── Entities/
│   ├── Services/
│   ├── IntegrationServices/        # For inter-service communication
│   ├── Data/                       # DbContext (implements IHasEventInbox, IHasEventOutbox)
│   └── OrderingServiceModule.cs
├── OrderingService.Contracts/      # Interfaces, DTOs, ETOs (shared)
└── OrderingService.Tests/

Inter-Service Communication

1. Integration Services (Synchronous HTTP)

For synchronous calls, use Integration Services - NOT regular application services.

Step 1: Provider Service - Create Integration Service

csharp
// In CatalogService.Contracts project
[IntegrationService]
public interface IProductIntegrationService : IApplicationService
{
    Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids);
}

// In CatalogService project
[IntegrationService]
public class ProductIntegrationService : ApplicationService, IProductIntegrationService
{
    public async Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids)
    {
        var products = await _productRepository.GetListAsync(p => ids.Contains(p.Id));
        return ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
    }
}

Step 2: Provider Service - Expose Integration Services

csharp
// In CatalogServiceModule.cs
Configure<AbpAspNetCoreMvcOptions>(options =>
{
    options.ExposeIntegrationServices = true;
});

Step 3: Consumer Service - Add Package Reference

Add reference to provider's Contracts project (via ABP Studio or manually):

  • Right-click OrderingService → Add Package Reference → Select CatalogService.Contracts

Step 4: Consumer Service - Generate Proxies

bash
# Run ABP CLI in consumer service folder
abp generate-proxy -t csharp -u http://localhost:44361 -m catalog --without-contracts

Or use ABP Studio: Right-click service → ABP CLI → Generate Proxy → C#

Step 5: Consumer Service - Register HTTP Client Proxies

csharp
// In OrderingServiceModule.cs
[DependsOn(typeof(CatalogServiceContractsModule))] // Add module dependency
public class OrderingServiceModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        // Register static HTTP client proxies
        context.Services.AddStaticHttpClientProxies(
            typeof(CatalogServiceContractsModule).Assembly,
            "CatalogService");
    }
}

Step 6: Consumer Service - Configure Remote Service URL

json
// appsettings.json
"RemoteServices": {
    "CatalogService": {
        "BaseUrl": "http://localhost:44361"
    }
}

Step 7: Use Integration Service

csharp
public class OrderAppService : ApplicationService
{
    private readonly IProductIntegrationService _productIntegrationService;

    public async Task<List<OrderDto>> GetListAsync()
    {
        var orders = await _orderRepository.GetListAsync();
        var productIds = orders.Select(o => o.ProductId).Distinct().ToList();

        // Call remote service via generated proxy
        var products = await _productIntegrationService.GetProductsByIdsAsync(productIds);
        // ...
    }
}

Why Integration Services? Application services are for UI - they have different authorization, validation, and optimization needs. Integration services are designed specifically for inter-service communication.

When to use: Need immediate response, data required to complete current operation (e.g., get product details to display in order list).

2. Distributed Events (Asynchronous)

Use RabbitMQ-based events for loose coupling.

When to use:

  • Notifying other services about state changes (e.g., "order placed", "stock updated")
  • Operations that don't need immediate response
  • When services should remain independent and decoupled
csharp
// Define ETO in Contracts project
[EventName("Product.StockChanged")]
public class StockCountChangedEto
{
    public Guid ProductId { get; set; }
    public int NewCount { get; set; }
}

// Publish
await _distributedEventBus.PublishAsync(new StockCountChangedEto { ... });

// Subscribe in another service
public class StockChangedHandler : IDistributedEventHandler<StockCountChangedEto>, ITransientDependency
{
    public async Task HandleEventAsync(StockCountChangedEto eventData) { ... }
}

DbContext must implement IHasEventInbox, IHasEventOutbox for Outbox/Inbox pattern.

Performance: Entity Cache

For frequently accessed data from other services, use Entity Cache:

csharp
// Register
context.Services.AddEntityCache<Product, ProductDto, Guid>();

// Use - auto-invalidates on entity changes
private readonly IEntityCache<ProductDto, Guid> _productCache;

public async Task<ProductDto> GetProductAsync(Guid id)
{
    return await _productCache.GetAsync(id);
}

Pre-Configured Infrastructure

  • RabbitMQ - Distributed events with Outbox/Inbox
  • Redis - Distributed cache and locking
  • YARP - API Gateway
  • OpenIddict - Auth server

Best Practices

  • Choose communication wisely - Synchronous for queries needing immediate data, asynchronous for notifications and state changes
  • Use Integration Services - Not application services for inter-service calls
  • Cache remote data - Use Entity Cache or IDistributedCache for frequently accessed data
  • Share only Contracts - Never share implementations
  • Idempotent handlers - Events may be delivered multiple times
  • Database per service - Each service owns its database