Services Overview¶
This section is the index of application services in the Cargonerds solution. Application services are the entry points to business logic: they implement use cases, orchestrate repositories and domain logic, accept and return DTOs, and are auto-exposed as REST endpoints by ABP.
This page explains the concept, lists every service grouped by the module that owns it, documents the base classes they derive from, and points at the deeper per-service pages.
ABP reference
See the ABP Application Services documentation for the framework concepts this page builds on.
What application services do¶
- Implement use cases and coordinate domain objects
- Work through repositories (generic
IRepository<TEntity, Guid>or custom interfaces) - Return DTOs to the presentation layer (never raw entities)
- Enforce authorization and validation
- Run inside an ABP Unit of Work, and are turned into REST controllers by Auto API Controllers
In this codebase the application layer is split across three ABP modules stacked by [DependsOn]
(see Module system):
graph TD
Host["Cargonerds host<br/>(src/Cargonerds.Application)<br/>identity, API keys, comments, org units"]
Pricing["Pricing<br/>(modules/pricing)<br/>quotations, offers"]
Hub["Hub<br/>(modules/hub)<br/>shipments, orders, documents, …"]
Host -->|DependsOn| Pricing
Host -->|DependsOn| Hub
Pricing -->|DependsOn| Hub
- Hub (
modules/hub) is the dominant layer — almost all real business logic lives here. - Pricing (
modules/pricing) is a satellite of Hub; itsPricingApplicationModuleDependsOnHubApplicationModuleand reuses Hub's base service and Mapperly mapper. Notably, the pricing domain entities live in the Hub module (Hub.Entities.Pricing.*, persisted byHubDbContext), not inPricing.Domain— see Pricing module. - Cargonerds host (
src) is a thin glue layer that mostly extends or replaces ABP framework services rather than owning domain logic.
Scale
There are roughly 35 *AppService classes across the three *.Application projects (about
34 types whose name ends in AppService, two of which are the abstract HubAppService /
CargonerdsAppService bases).
The dependency root is CargonerdsApplicationModule
(src/Cargonerds.Application/CargonerdsApplicationModule.cs), which DependsOn both
PricingApplicationModule and HubApplicationModule plus a long list of ABP Pro modules
(Identity, Account, SaaS, Chat, Audit Logging, OpenIddict, FileManagement, CmsKit Pro, LeptonX, …).
Service organization¶
Cargonerds host services¶
Located in src/Cargonerds.Application/ (namespaces under Cargonerds.*). These extend or replace
ABP framework services and wire identity/users into the freight domain.
| Service | Description | Documentation |
|---|---|---|
IdentityUserAppService |
Replaces the ABP identity-user service | Internal |
UserImportAppService |
Bulk user import | Internal |
OrganizationUnitAppService |
Replaces the ABP org-unit service; org-code / owner wiring | Internal |
ApiKeyAppService / ApiKeyPermissionAppService |
API keys and their permissions | Internal |
ClientSettingsAppService / CustomScriptSettingAppService |
Client-specific & custom-script settings | Internal |
CurrentOrganizationAppService |
Current organization context | Internal |
CommentPublicAppService |
CmsKit comments (public surface) | Internal |
GlobalSearchAppService |
Cross-entity search orchestrator (runs Hub providers) | Internal |
SecurityLogExportAppService |
Identity security-log export | Internal |
Hub module services¶
Located in modules/hub/src/Hub.Application/Services/ (namespace Hub.Services). This is where the
bulk of the freight-domain use cases live.
| Service | Description | Documentation |
|---|---|---|
ShipmentAppService |
Shipments / consols and CW1 booking | Shipment Service |
HubOrganizationAppService |
Organizations (trading partners) | Organization Service |
DocumentAppService |
Document metadata, blob streaming, view tokens | Document Service |
OrderAppService / PurchaseOrderAppService |
Sales orders & purchase orders | Internal |
BrokerageAppService |
Customs brokerage | Internal |
MarginAppService / CalculatedShipmentAppService |
Calculated / financial read-models | Internal |
NotificationAppService / NotificationSettingAppService |
Notifications & notification settings | Internal |
DashboardAppService / MyInsightsAppService / InsightShipmentsAppService |
Dashboards & insights | Internal |
ProfileAppService / TagAppService |
User profile & tagging | Internal |
CommodityAppService |
Commodity code entity | Internal |
ExcelExportAppService |
Excel export (background-job backed) | Internal |
TemplatePreviewAppService |
Email/text-template preview & send | Internal |
Insights service naming
The file Services/Insights/InsightsChartAppService.cs actually declares the class
InsightShipmentsAppService — the file name and class name differ.
Pricing module services¶
Located in modules/pricing/src/Pricing.Application/Quotation/ (namespace Pricing.Quotation).
| Service | Description |
|---|---|
QuotationAppService |
Quotation CRUD; enqueues the quote-generation background job |
QuotationLegacyAppService |
The engine: calls the external pricing API, maps the response, applies margins |
OfferAppService |
Manual offer CRUD; recomputes totals |
Base service classes¶
Every service ultimately derives from ABP's ApplicationService, usually through one of two
module-specific bases, and many Hub/Pricing services derive from a third, custom CRUD base.
ApplicationService (ABP)¶
The framework base for all services; provides ObjectMapper, CurrentUser, CurrentTenant,
Logger, LazyServiceProvider, AuthorizationService, and localization (L).
CargonerdsAppService and HubAppService¶
Thin module bases that just set the localization resource (and, for Hub, the object-mapper context):
// src/Cargonerds.Application/CargonerdsAppService.cs
public abstract class CargonerdsAppService : ApplicationService
{
protected CargonerdsAppService()
{
LocalizationResource = typeof(CargonerdsResource);
}
}
// modules/hub/src/Hub.Application/HubAppService.cs
public abstract class HubAppService : ApplicationService
{
protected HubAppService()
{
LocalizationResource = typeof(HubResource);
ObjectMapperContext = typeof(HubApplicationModule);
}
}
HubEntityBaseService<…> — the project's real CRUD/list engine¶
Most Hub and Pricing read/list services do not derive from ABP's CrudAppService. They derive
from the custom HubEntityBaseService in
modules/hub/src/Hub.Application/Services/HubEntityBaseService.cs. It implements
IReadOnlyAppService<TDto, Guid, TListParamDto> and centralizes list/paging, OData-style filtering,
faceting, caching, org-unit filtering and a ReadPermission hook. Subclasses implement ToDto and
override hooks as needed.
There are two generic overloads — a 4-generic full form and a 3-generic convenience form that
defaults TListParamDto to PagedAndSortedResultRequestWithFacetOptionsDto:
// modules/hub/src/Hub.Application/Services/HubEntityBaseService.cs
[ExposeServices(IncludeSelf = true)]
public abstract class HubEntityBaseService<TEntity, TDto, TListParamDto, TService>(
IRepository<TEntity, Guid> repository,
IServiceProvider serviceProvider
) : HubAppService, IReadOnlyAppService<TDto, Guid, TListParamDto>
where TEntity : class, IEntity, IEntity<Guid>
where TListParamDto : PagedAndSortedResultRequestWithFacetOptionsDto
where TService : HubEntityBaseService<TEntity, TDto, TListParamDto, TService>
{
[return: NotNullIfNotNull(nameof(entity))]
protected abstract TDto? ToDto(TEntity? entity);
protected virtual string ReadPermission => string.Empty;
public virtual async Task<PagedResultDtoWithFilters<TDto>> GetListAsync(TListParamDto input, bool force);
}
A concrete service is typically tiny — it points the base at a repository, supplies a ToDto
delegate (usually a Mapperly extension method), and declares its read permission:
// modules/hub/src/Hub.Application/Services/OrderAppService.cs
public class OrderAppService(IRepository<Order, Guid> repository, IServiceProvider serviceProvider)
: HubEntityBaseService<Order, OrderDto, OrderAppService>(repository, serviceProvider),
IOrderAppService
{
protected override OrderDto? ToDto(Order? entity) => entity?.ToDto();
protected override string ReadPermission => HubPermissions.Orders.View;
}
ShipmentAppService (modules/hub/src/Hub.Application/Services/ShipmentAppService.cs) is the richest
example: it extends HubEntityBaseService<ShipmentBase, CombinedShipmentDto, ShipmentPageRequestDto,
ShipmentAppService>, implements IShipmentAppService : ICrudAppService<CombinedShipmentDto, Guid,
ShipmentPageRequestDto>, and does permission-gated includes — e.g. it conditionally includes
documents and invoices based on AuthorizationService.IsGrantedAnyAsync(...):
// modules/hub/src/Hub.Application/Services/ShipmentAppService.cs
protected override async Task<IQueryable<ShipmentBase>> WithDetailsAsync()
{
IQueryable<ShipmentBase> query = await base.WithDetailsAsync();
var filterCtx = Get<CurrentFilterContextProvider>();
return query
.IncludeFilteredDocumentsIf(filterCtx,
await AuthorizationService.IsGrantedAnyAsync(HubPermissions.Document.View))
.IncludeInvoicesAndRevenuesIf(filterCtx,
await AuthorizationService.IsGrantedAnyAsync(HubPermissions.Invoice.View));
}
How a read flows through the base¶
For both GetAsync(string id) and GetListAsync(input, force) the pipeline is:
CheckPermissionAsync(ReadPermission)— no-op when the permission string is empty, otherwiseIAuthorizationService.CheckAsync(permission).- Wrap the result in the distributed application cache (
IAppCache) viaCache.GetOrAddAsync(...), keyed on the input, tagged withtypeof(TService).Name.force || !IsCacheEnabledAsyncbypasses the cache. Cache durations are feature-driven (HubFeatures.Cache.*, defaults 15 min absolute / 2 min refresh). - Build the query through
WithListDetailsAsync()→FilterAllQueryFiltersAsync(applies org/tenant filters via.FilterAll(serviceProvider)andOrderBy) →PaginationAsync, which applies the OData query (ApplyAllQueryApplier), counts, skip/takes, maps viaToDto, then builds facets and returns aPagedResultDtoWithFilters<TDto>.
Helper hooks on the base
HubEntityBaseService also exposes ValidateUserAccessToOrgUnitAsync,
ValidateUserAccessToShipmentDataAsync, ValidateIsCurrentUserAsync, Get<T>() for
service-locator access, InvalidateCacheAsync / WithCacheInvalidationAsync(...) for writes, and
FormatDateIf(...) using UserPreferences.DateFormat.
Service registration, replacement and decoration¶
ABP's dependency injection conventions register app services automatically. This codebase uses several override patterns:
[ExposeServices(IncludeSelf = true)]onHubEntityBaseService(andShipmentAppService) so the concrete type stays resolvable — the base re-resolvesTServicefrom DI inside its cache factory (sp.GetRequiredService<TService>()).- Service replacement via
[Dependency(ReplaceServices = true)]+[ExposeServices(...)]:IdentityUserAppServicereplaces ABP'sIIdentityUserAppService, andOrganizationUnitAppServicereplacesIOrganizationUnitAppService. - Decoration of an ABP service:
ApiKeyPermissionAppServiceis marked[DisableConventionalRegistration]and registered as a decorator inCargonerdsApplicationModule:
// src/Cargonerds.Application/CargonerdsApplicationModule.cs
context.Services.Decorate<IPermissionAppService, ApiKeyPermissionAppService>();
Common DTO patterns¶
ABP paging request/result types are used throughout (see DTOs):
public class PagedAndSortedResultRequestDto { /* SkipCount, MaxResultCount, Sorting */ }
public class PagedResultDto<T> { /* TotalCount, Items */ }
Hub list endpoints extend the result envelope with faceting and OData metadata:
// modules/hub/src/Hub.Application.Contracts/Dtos/PagedResultDtoWithFilters.cs
public class PagedResultDtoWithFilters<T> : PagedResultDto<T>, IFacetResponse
{
public List<FilterGroupDto> Filters { get; set; } = new(); // filter groups for the UI
public List<AppliedFilterDto> Applied { get; set; } = new(); // active filters as chips
public string? ODataFilter { get; set; } // combined OData filter
public QueryMetaDto Query { get; set; } = new(); // $orderby/$top/$skip metadata
}
Request DTOs extend the ABP paging DTOs with facet options
(PagedAndSortedResultRequestWithFacetOptionsDto, FilterablePagedAndSortedResultRequestDto,
ShipmentPageRequestDto). See DTOs for the generated-DTO and
Mapperly mapping story.
Authorization¶
Permissions are declared as constant trees in HubPermissions, CargonerdsPermissions and
PricingPermissions (in the matching *.Application.Contracts projects) and enforced two ways, both
present in this codebase:
- Declarative
[Authorize(...)]on classes/methods (e.g.ExcelExportAppService,NotificationAppService, the Pricing services). - Imperative via the base
CheckPermissionAsync/AuthorizationService.IsGrantedAnyAsyncinsideHubEntityBaseService-derived services.
[Authorize(HubPermissions.Shipment.Edit)]
public Task<TagDto> CreateAsync(CreateUpdateTagDto input) { /* ... */ }
Grants are attribute-driven and hierarchical
Permission constants carry a custom [GrantInRoles(...)] attribute that
Cargonerds.Domain/RolesDataSeedContributor.cs reads reflectively to auto-grant permissions to
roles at seed time. With Inherit = true (the default) a grant walks up
RoleConsts.Hierarchy (DefaultCustomer → Operator → AccountOwner → LocalRealtimeAdmin →
SuperUser), so a permission granted to a lower role is also granted to every role above it.
Adding a permission constant without [GrantInRoles] means no role receives it by seeding.
See Authorization.
Auto-API and remote services¶
Service interfaces are conventional ABP (ICrudAppService<…>, IReadOnlyAppService<…>) and feed
ABP's auto-API controllers
and dynamic C# client proxies.
Each module names its remote service — e.g. HubRemoteServiceConsts declares
RemoteServiceName = "Hub", ModuleName = "hub". FluentValidation validators in
*.Application.Contracts/Validators/ are auto-applied to incoming DTOs via AbpFluentValidationModule.
HubEntityBaseService exposes only the two-argument list/get methods
On the base, GetAsync(Guid) and the single-argument GetListAsync(input) are marked
[RemoteService(IsEnabled = false)]. The live endpoints are the string-keyed
GetAsync(string id) and the two-argument GetListAsync(input, force). Because reads are cached
by service-type tag, writes must call InvalidateCacheAsync / WithCacheInvalidationAsync or
stale data (up to the configured TTL) is served. See Caching.
Service lifecycle¶
graph TD
A[HTTP Request] --> B[Auto-API Controller]
B --> C[Application Service]
C --> D[Authorization Check]
D --> E[DTO Validation]
E --> F[Begin Unit of Work]
F --> G{Cache hit?}
G -- yes --> K[Return cached DTO]
G -- no --> H[FilterAll org/tenant + OData query]
H --> I[Repository / Domain Logic]
I --> J[Map to DTO via ToDto / Mapperly]
J --> L[Build facets + cache result]
L --> M[Commit Unit of Work]
M --> N[Return Response]
Next steps¶
Per-service deep dives:
- Shipment Service — core Hub aggregate
- Organization Service
- Document Service
Cross-cutting implementation detail:
- Application Services
- Service Patterns
- DTOs
- Authorization
- Caching — the
IAppCachetag-based cache the base service uses - Architecture overview and Module overview