Skip to content

Services Overview

This section provides comprehensive documentation for all application services in the Cargonerds solution.

What are Application Services?

Application services are the entry points to the application's business logic. They:

  • Implement use cases and coordinate domain objects
  • Work with repositories and domain services
  • Return Data Transfer Objects (DTOs) to the presentation layer
  • Handle authorization and validation
  • Manage transactions through Unit of Work

Service Organization

Services in Cargonerds are organized by module and business domain:

Cargonerds Core Services

Located in src/Cargonerds.Application/:

Service Description Documentation
BookAppService Manages books (sample domain) Book Service
ClientSettingsAppService Manages client-specific settings Internal
CurrentOrganizationAppService Provides current organization context Internal
OrganizationUnitAppService Manages organization units Internal

Hub Module Services

Located in modules/hub/src/Hub.Application/Services/:

Service Description Documentation
ShipmentAppService Manages shipments and logistics Shipment Service
HubOrganizationAppService Manages organizations in Hub Organization Service
DocumentAppService Manages shipment documents Document Service
OrderAppService Manages orders Internal
ProfileAppService Manages user profiles Internal
TagAppService Manages tags for categorization Internal

Common Service Patterns

CRUD Operations

Most services follow a standard CRUD pattern:

public interface IBookAppService : IApplicationService
{
    Task<BookDto> GetAsync(Guid id);
    Task<PagedResultDto<BookDto>> GetListAsync(PagedAndSortedResultRequestDto input);
    Task<BookDto> CreateAsync(CreateUpdateBookDto input);
    Task<BookDto> UpdateAsync(Guid id, CreateUpdateBookDto input);
    Task DeleteAsync(Guid id);
}

Pagination

Services use ABP's pagination DTOs:

public class PagedAndSortedResultRequestDto
{
    public int SkipCount { get; set; }  // Number of records to skip
    public int MaxResultCount { get; set; }  // Max records to return
    public string Sorting { get; set; }  // Sort expression
}

public class PagedResultDto<T>
{
    public long TotalCount { get; set; }
    public List<T> Items { get; set; }
}

Filtering

Hub services support advanced filtering:

public class PagedAndSortedResultRequestWithFacetOptionsDto : PagedAndSortedResultRequestDto
{
    public Dictionary<string, List<string>> Filters { get; set; }
    public bool IncludeFacets { get; set; }
}

public class PagedResultDtoWithFilters<T> : PagedResultDto<T>
{
    public List<FacetResult> Facets { get; set; }
    public List<AppliedFilter> AppliedFilters { get; set; }
}

Authorization

All services require appropriate permissions:

[Authorize(CargonerdsPermissions.Books.Default)]
public class BookAppService : ApplicationService
{
    [Authorize(CargonerdsPermissions.Books.Create)]
    public async Task<BookDto> CreateAsync(CreateUpdateBookDto input)
    {
        // Implementation
    }
}

Base Service Classes

ApplicationService

Base class for all application services:

public abstract class ApplicationService : IApplicationService
{
    // Provides access to:
    // - ObjectMapper (for DTO mapping)
    // - CurrentUser (current user info)
    // - CurrentTenant (current tenant info)
    // - Logger (for logging)
    // - LazyServiceProvider (for lazy injection)
    // - L (for localization)
}

HubEntityBaseService

Custom base service for Hub entities:

public abstract class HubEntityBaseService<TEntity, TDto> : ApplicationService
    where TEntity : class, IEntity
{
    // Provides:
    // - Common CRUD operations
    // - Pagination support
    // - Filtering and faceting
    // - Organization context
    // - Permission checking

    protected abstract TDto? ToDto(TEntity? entity);
    protected abstract string ReadPermission { get; }
    protected virtual Task<IQueryable<TEntity>> WithDetailsAsync();
}

Error Handling

Services use ABP's exception handling:

// User-friendly errors
throw new UserFriendlyException("Book not found");

// Business rule violations
throw new BusinessException("Books:DuplicateISBN")
    .WithData("ISBN", input.ISBN);

// Authorization errors (handled by ABP)
[Authorize(CargonerdsPermissions.Books.Create)]

Validation

DTOs are automatically validated:

public class CreateUpdateBookDto
{
    [Required]
    [StringLength(128)]
    public string Name { get; set; }

    [Required]
    public BookType Type { get; set; }

    [Required]
    [DataType(DataType.Date)]
    public DateTime PublishDate { get; set; }

    [Required]
    [Range(0, 999.99)]
    public float Price { get; set; }
}

Unit of Work

All service methods are automatically wrapped in a Unit of Work:

[UnitOfWork]
public virtual async Task CreateBookWithRelationsAsync(CreateBookDto input)
{
    // All operations in one transaction
    var author = await _authorRepository.InsertAsync(...);
    var book = await _bookRepository.InsertAsync(...);
    var category = await _categoryRepository.InsertAsync(...);
    // Auto-committed if no exception
}

Object Mapping

Services use AutoMapper through ObjectMapper:

public async Task<BookDto> GetAsync(Guid id)
{
    var book = await _repository.GetAsync(id);

    // Map entity to DTO
    return ObjectMapper.Map<Book, BookDto>(book);
}

public async Task<BookDto> CreateAsync(CreateUpdateBookDto input)
{
    // Map DTO to entity
    var book = ObjectMapper.Map<CreateUpdateBookDto, Book>(input);
    await _repository.InsertAsync(book);

    return ObjectMapper.Map<Book, BookDto>(book);
}

Localization

Services can use localized strings:

public async Task ValidateAsync(Book book)
{
    if (book.Price < 0)
    {
        throw new UserFriendlyException(
            L["NegativePriceNotAllowed"]
        );
    }
}

Testing Services

Application services can be easily tested:

public class BookAppService_Tests : CargonerdsApplicationTestBase
{
    private readonly IBookAppService _bookAppService;

    public BookAppService_Tests()
    {
        _bookAppService = GetRequiredService<IBookAppService>();
    }

    [Fact]
    public async Task Should_Get_Book()
    {
        var book = await _bookAppService.CreateAsync(new CreateUpdateBookDto
        {
            Name = "Test Book",
            Type = BookType.ScienceFiction,
            PublishDate = DateTime.Now,
            Price = 19.99f
        });

        var result = await _bookAppService.GetAsync(book.Id);

        result.ShouldNotBeNull();
        result.Name.ShouldBe("Test Book");
    }
}

API Exposure

Application services are automatically exposed as REST APIs:

GET /api/app/book/{id}
GET /api/app/book
POST /api/app/book
PUT /api/app/book/{id}
DELETE /api/app/book/{id}

Best Practices

  1. One Service Per Aggregate: Create one application service for each aggregate root
  2. Use DTOs: Never expose domain entities directly
  3. Authorization First: Always protect operations with permissions
  4. Keep Methods Focused: Each method should implement one use case
  5. Handle Errors Gracefully: Use appropriate exception types
  6. Log Important Operations: Use the built-in logger
  7. Consider Performance: Use pagination for large datasets
  8. Test Thoroughly: Write unit tests for business logic

Service Lifecycle

graph TD
    A[HTTP Request] --> B[Controller/API Endpoint]
    B --> C[Application Service]
    C --> D[Authorization Check]
    D --> E[DTO Validation]
    E --> F[Begin Unit of Work]
    F --> G[Repository Operations]
    G --> H[Domain Logic]
    H --> I[Commit Transaction]
    I --> J[Map to DTO]
    J --> K[Return Response]

Next Steps

Explore detailed documentation for specific services:

For implementation details, see: - Application Services Guide - Authorization - Data Transfer Objects