| name | abp-microservice |
| description | ABP Microservice solution template - service structure, Integration Services ([IntegrationService]), inter-service HTTP proxies, distributed events with Outbox/Inbox, Entity Cache, RabbitMQ/Redis/YARP setup. Use when working with the ABP microservice solution template or inter-service communication patterns. |
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
[IntegrationService]
public interface IProductIntegrationService : IApplicationService
{
Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids);
}
[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
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
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
[DependsOn(typeof(CatalogServiceContractsModule))]
public class OrderingServiceModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddStaticHttpClientProxies(
typeof(CatalogServiceContractsModule).Assembly,
"CatalogService");
}
}
Step 6: Consumer Service - Configure Remote Service URL
"RemoteServices": {
"CatalogService": {
"BaseUrl": "http://localhost:44361"
}
}
Step 7: Use Integration Service
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();
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
[EventName("Product.StockChanged")]
public class StockCountChangedEto
{
public Guid ProductId { get; set; }
public int NewCount { get; set; }
}
await _distributedEventBus.PublishAsync(new StockCountChangedEto { ... });
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:
context.Services.AddEntityCache<Product, ProductDto, Guid>();
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