一键导入
the-standard-architecture
Enforces Standard-compliant modeling, brokers, services, aggregation, exposers, REST APIs, and UI architecture.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Enforces Standard-compliant modeling, brokers, services, aggregation, exposers, REST APIs, and UI architecture.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | The Standard Architecture |
| description | Enforces Standard-compliant modeling, brokers, services, aggregation, exposers, REST APIs, and UI architecture. |
| the standard version | v2.13.0 |
| skill version | v0.3.0.0 |
This skill operationalizes the architecture chapters of The Standard. It governs how systems are decomposed, how responsibilities are assigned, and how dependencies flow. It includes the architecture of models, brokers, services, aggregation, exposers, communication protocols, APIs, and user interfaces.
This skill explicitly covers:
0.1.2 Modeling
1 Brokers
2 Services
3 Exposers
3.1 Communication Protocols
3.2 User Interfaces
The following topics are governed by dedicated skills that extend this skill.
See ## Related skills for activation guidance:
the-standard-eventsthe-standard-versioningUse this skill for system design, architecture review, decomposition, refactoring, API design, UI architecture, dependency-flow design, or when deciding where behavior belongs.
This skill defines the structural rules for the entire system. When a specific architectural pattern is chosen, a dedicated skill governs its detailed implementation. Activate the relevant skill as soon as the pattern is identified -- do not wait until implementation is underway.
| Pattern | Skill | Activate when |
|---|---|---|
| CulDeSac eventing: event broker, event service, publish/subscribe, DI registration, startup activation | the-standard-events | An orchestration service needs to publish a domain event or subscribe to one without a synchronous return |
| Release versioning, file versioning, API versioning, deprecation | the-standard-versioning | A model, service, or API contract change is introduced, a release is being cut, or existing code must be deprecated |
Every publicly exposed interface method — on brokers, services, and exposers — must
return ValueTask or ValueTask<T>, even if the current implementation does not
internally await anything.
This is The Standard §1.5.1: the public contract is uniformly async so callers never need to change if an implementation later becomes truly asynchronous.
| Pattern | Verdict |
|---|---|
public async ValueTask LogWarningAsync(string message) => this.logger.LogWarning(message); | Correct — async keyword, direct call |
public ValueTask<IQueryable<Student>> SelectAllStudentsAsync() => ValueTask.FromResult(...) | Correct — ValueTask.FromResult wraps sync result |
public async ValueTask<IQueryable<Student>> SelectAllStudentsAsync() => await this.SelectAllAsync<Student>(); | Correct — async/await through generic helper |
public IQueryable<Student> SelectAllStudents() => this.Students.AsNoTracking(); | WRONG — synchronous, no ValueTask |
public ValueTask LogWarningAsync(string message) => new ValueTask(Task.Run(() => ...)); | WRONG — Task.Run wraps a synchronous call needlessly |
Consequence for services: CreateAndLog* helpers must be async too, because
ILoggingBroker.LogErrorAsync returns ValueTask. Catch blocks must use throw await:
// WRONG
private StudentValidationException CreateAndLogValidationException(Xeption exception)
{
...
this.loggingBroker.LogError(studentValidationException); // no such sync method
return studentValidationException;
}
// CORRECT
private async ValueTask<StudentValidationException> CreateAndLogValidationException(
Xeption exception)
{
var studentValidationException = new StudentValidationException(...);
await this.loggingBroker.LogErrorAsync(studentValidationException);
return studentValidationException;
}
// CORRECT call site in TryCatch
catch (NullStudentException nullStudentException)
{
throw await CreateAndLogValidationException(nullStudentException);
}
the-standard-events skill -- it governs the event broker, event service, validation, exception handling, DI lifetime, and startup activation for the event layer.This skill governs what the correct structure is at each layer.
When a structural change breaks an existing contract -- a model property added or removed, a service signature changed, an API route or response shape altered -- it is no longer purely an architecture decision.
Activate the-standard-versioning skill at that point.
The versioning skill governs:
Vn subfolders without overwriting earlier versionsThe architecture skill and the versioning skill are complementary:
When reviewing or generating architecture, verify all of the following:
the-standard-events skill has been activated and its rules are satisfied.the-standard-versioning skill has been activated and its rules are satisfied.The following addendum preserves the supplied implementation profile so the skill can enforce both the abstract Standard and the concrete .NET implementation style you attached.
The Standard is an opinionated software engineering standard authored by Hassan Habib. It prescribes a tri-nature architecture consisting of:
| Layer | Responsibility |
|---|---|
| Brokers | Thin abstraction over any external dependency (DB, API, logs) |
| Services | All business logic — validation, orchestration, coordination |
| Exposers | Entry points that expose services (Controllers, Endpoints) |
This project currently implements Brokers and Foundation Services for the
LegacyUser entity (storage-based) and the Person entity (API-based), as well as
an API Broker for sending Person entities to an external API.
RedRhino.Core.Synchronizer/
├── Brokers/
│ ├── Apis/
│ │ ├── IModernApiBroker.cs (partial interface — base)
│ │ ├── IModernApiBroker.Persons.cs (partial interface — entity)
│ │ ├── ModernApiBroker.cs (partial class — base)
│ │ └── ModernApiBroker.Persons.cs (partial class — entity)
│ ├── Loggings/
│ │ ├── ILoggingBroker.cs
│ │ └── LoggingBroker.cs
│ └── Storages/
│ ├── IStorageBroker.cs (partial interface — base)
│ ├── IStorageBroker.LegacyUsers.cs (partial interface — entity)
│ ├── StorageBroker.cs (partial class — base)
│ └── StorageBroker.LegacyUsers.cs (partial class — entity)
├── Models/
│ ├── Foundations/
│ ├── Persons/
│ │ ├── Person.cs
│ │ ├── PersonType.cs
│ │ ├── PersonRecordState.cs
│ │ └── Exceptions/
│ │ ├── NullPersonException.cs
│ │ ├── InvalidPersonException.cs
│ │ ├── AlreadyExistsPersonException.cs
│ │ ├── FailedPersonDependencyException.cs
│ │ ├── FailedPersonServiceException.cs
│ │ ├── PersonValidationException.cs
│ │ ├── PersonDependencyException.cs
│ │ ├── PersonDependencyValidationException.cs
│ │ └── PersonServiceException.cs
│ └── LegacyUsers/
│ ├── LegacyUser.cs
│ └── Exceptions/
│ ├── NullLegacyUserException.cs
│ ├── InvalidLegacyUserException.cs
│ ├── AlreadyExistsLegacyUserException.cs
│ ├── FailedStorageLegacyUserDependencyException.cs
│ ├── FailedLegacyUserServiceException.cs
│ ├── LegacyUserValidationException.cs
│ ├── LegacyUserDependencyException.cs
│ ├── LegacyUserDependencyValidationException.cs
│ └── LegacyUserServiceException.cs
├── Services/
│ └── Foundations/
│ ├── LegacyUsers/
│ │ ├── ILegacyUserService.cs
│ │ ├── LegacyUserService.cs (partial — logic)
│ │ ├── LegacyUserService.Validations.cs (partial — validations)
│ │ └── LegacyUserService.Exceptions.cs (partial — exception handling)
│ └── Persons/
│ ├── IPersonService.cs
│ ├── PersonService.cs (partial — logic)
│ ├── PersonService.Validations.cs (partial — validations)
│ └── PersonService.Exceptions.cs (partial — exception handling)
└── Program.cs
RedRhino.Core.Synchronizer.Tests.Units/
└── Services/
└── Foundations/
├── LegacyUsers/
│ ├── LegacyUserServiceTests.cs (partial — setup & helpers)
│ ├── LegacyUserServiceTests.Logic.{Method}.cs (partial — happy-path tests)
│ ├── LegacyUserServiceTests.Validations.{Method}.cs (partial — validation tests)
│ └── LegacyUserServiceTests.Exceptions.{Method}.cs (partial — exception tests)
└── Persons/
├── PersonServiceTests.cs (partial — setup & helpers)
├── PersonServiceTests.Logic.{Method}.cs (partial — happy-path tests)
├── PersonServiceTests.Validations.{Method}.cs (partial — validation tests)
└── PersonServiceTests.Exceptions.{Method}.cs (partial — exception tests)
Brokers are thin wrappers around external resources. They contain zero business logic.
Rule — Generic Helpers: Every broker base partial must expose private generic helper methods (e.g.,
InsertAsync<T>,PostAsync<T>) that encapsulate the underlying client calls. Entity partial files never reference the private client member (e.g.,this.apiClient,this.Entry(...)) directly — they delegate to the generic helpers instead. This keeps entity partials decoupled from the concrete client and allows swapping the underlying implementation in a single place.
| Aspect | Implementation |
|---|---|
| Base class | EFxceptionsContext (from the EFxceptions library — wraps DbContext with meaningful EF exceptions) |
| Interface | partial interface IStorageBroker — split per entity |
| Class | partial class StorageBroker — split per entity |
| Generic CRUD helpers | Private helpers in base partial: InsertAsync<T>, SelectAllAsync<T>, SelectAsync<T>, UpdateAsync<T>, DeleteAsync<T> |
| Configuration | Reads DefaultConnection from IConfiguration; calls this.Database.Migrate() at construction |
Base partial — StorageBroker.cs
This file owns: IConfiguration, OnConfiguring, the constructor (with Database.Migrate()),
and private generic CRUD helpers. It owns nothing else.
arch-014 — No
DbSet<>in the base partial.DbSet<Student> Studentslives inStorageBroker.Students.cs, not here. The base partial must never declare entity-specific members.
public partial class StorageBroker : EFxceptionsContext, IStorageBroker
{
// No DbSet<> properties here. Each entity partial declares its own.
private readonly IConfiguration configuration;
public StorageBroker(IConfiguration configuration)
{
this.configuration = configuration;
// arch-011: Must be called — applies pending migrations at startup.
// Omitting this means the schema is never applied.
this.Database.Migrate();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
string connectionString =
this.configuration.GetConnectionString("DefaultConnection");
optionsBuilder.UseSqlServer(connectionString);
}
// arch-012: All EF operations live here. Entity partials call these helpers only.
private async ValueTask<T> InsertAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Added;
await this.SaveChangesAsync();
return entity;
}
// SelectAllAsync<T> wraps IQueryable in a ValueTask so all entity methods
// follow the uniform async/await pattern. AsNoTracking() is applied here.
private ValueTask<IQueryable<T>> SelectAllAsync<T>() where T : class =>
ValueTask.FromResult<IQueryable<T>>(this.Set<T>().AsNoTracking());
private async ValueTask<T> SelectAsync<T>(Guid entityId) where T : class =>
await this.FindAsync<T>(entityId);
private async ValueTask<T> UpdateAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Modified;
await this.SaveChangesAsync();
return entity;
}
private async ValueTask<T> DeleteAsync<T>(T entity) where T : class
{
this.Entry(entity).State = EntityState.Deleted;
await this.SaveChangesAsync();
return entity;
}
}
Entity partial — StorageBroker.Students.cs
arch-014:
DbSet<Student>is declared here — in the entity partial — not inStorageBroker.cs.
public partial class StorageBroker
{
// DbSet<Student> belongs in this entity partial file, not in StorageBroker.cs.
public DbSet<Student> Students { get; set; }
public async ValueTask<Student> InsertStudentAsync(Student student) =>
await this.InsertAsync(student);
// arch-009: SelectAll* must be async ValueTask<IQueryable<T>>.
// WRONG: public IQueryable<Student> SelectAllStudents() => this.Students.AsNoTracking();
// WRONG: public IQueryable<Student> SelectAllStudents() => this.Set<Student>();
// CORRECT: delegate to SelectAllAsync<T>() — never touch DbSet or EF members directly.
public async ValueTask<IQueryable<Student>> SelectAllStudentsAsync() =>
await this.SelectAllAsync<Student>();
public async ValueTask<Student> SelectStudentByIdAsync(Guid studentId) =>
await this.SelectAsync<Student>(studentId);
public async ValueTask<Student> UpdateStudentAsync(Student student) =>
await this.UpdateAsync(student);
public async ValueTask<Student> DeleteStudentAsync(Student student) =>
await this.DeleteAsync(student);
}
Rule: Each entity gets its own partial file for both the interface and the class.
Rule — scope before scaffolding (arch-013): A branch named
BROKERS-student-insertimplements onlyInsertStudentAsync. If the prompt is ambiguous about which operations are needed, the agent must ask before creating the branch or writing any code. A single broker branch = a single operation.Rule — branch action language (prac-016): Broker branch actions use infrastructure verbs —
insert,select-all,select-by-id,update,delete. Never use business verbs (add,retrieve,modify,remove) in aBROKERSbranch name.
| Aspect | Implementation |
|---|---|
| HTTP client | IRESTFulApiFactoryClient (from the RESTFulSense library — wraps HttpClient) |
| Interface | partial interface IModernApiBroker — split per entity |
| Class | partial class ModernApiBroker — split per entity |
| Generic HTTP helpers | Private PostAsync<T>() on the base partial for reuse across entities |
| Configuration | Reads ApiConfigurations:Url from IConfiguration to set HttpClient.BaseAddress |
Base partial — ModernApiBroker.cs
public partial class ModernApiBroker : IModernApiBroker
{
private readonly IRESTFulApiFactoryClient apiClient;
public ModernApiBroker(IConfiguration configuration)
{
var httpClient = new HttpClient()
{
BaseAddress =
new Uri(configuration.GetValue<string>("ApiConfigurations:Url"))
};
this.apiClient = new RESTFulApiFactoryClient(httpClient);
}
private async ValueTask<T> PostAsync<T>(string relativeUrl, T content) =>
await this.apiClient.PostContentAsync<T>(relativeUrl, content);
}
Entity partial — ModernApiBroker.Persons.cs
public partial class ModernApiBroker
{
private const string PersonsRelativeUrl = "api/persons";
public async ValueTask<Person> PostPersonAsync(Person person) =>
await PostAsync(PersonsRelativeUrl, person);
}
Rule: Entity partials delegate to the generic helpers (
PostAsync<T>) — they never callthis.apiClientdirectly.
A dedicated abstraction over ILogger<T>. The broker exposes only async ValueTask methods
that correspond to standard log levels:
| Method | Log Level |
|---|---|
LogInformationAsync | Information |
LogTraceAsync | Trace |
LogDebugAsync | Debug |
LogWarningAsync | Warning |
LogErrorAsync | Error |
LogCriticalAsync | Critical |
LoggingBroker.cs
public class LoggingBroker : ILoggingBroker
{
private readonly ILogger<LoggingBroker> logger;
public LoggingBroker(ILogger<LoggingBroker> logger) =>
this.logger = logger;
public async ValueTask LogInformationAsync(string message) =>
this.logger.LogInformation(message);
public async ValueTask LogTraceAsync(string message) =>
this.logger.LogTrace(message);
public async ValueTask LogDebugAsync(string message) =>
this.logger.LogDebug(message);
public async ValueTask LogWarningAsync(string message) =>
this.logger.LogWarning(message);
public async ValueTask LogErrorAsync(Exception exception) =>
this.logger.LogError(exception, exception.Message);
public async ValueTask LogCriticalAsync(Exception exception) =>
this.logger.LogCritical(exception, exception.Message);
}
ILoggingBroker.cs
public interface ILoggingBroker
{
ValueTask LogInformationAsync(string message);
ValueTask LogTraceAsync(string message);
ValueTask LogDebugAsync(string message);
ValueTask LogWarningAsync(string message);
ValueTask LogErrorAsync(Exception exception);
ValueTask LogCriticalAsync(Exception exception);
}
Rule — async expression body, no Task.Run: Each method uses the
asynckeyword and delegates directly toILogger<T>. Never wrap the call inTask.Run()ornew ValueTask(Task.Run(...)).ILogger<T>is synchronous; wrapping it inTask.Run()introduces unnecessary thread-pool overhead and produces an inefficient heap-allocatedValueTask.Wrong:
public ValueTask LogWarningAsync(string message) => new ValueTask(Task.Run(() => this.logger.LogWarning(message)));Correct:
public async ValueTask LogWarningAsync(string message) => this.logger.LogWarning(message);
Rule — DI Registration: The logging broker must be explicitly registered in
Program.cs.AddLogging()(or the host builder's default) must also be present so thatILogger<LoggingBroker>resolves correctly.builder.Services.AddLogging(); builder.Services.AddTransient<ILoggingBroker, LoggingBroker>();Omitting either line causes a runtime DI resolution failure.
Entity classes are plain POCOs residing under Models/{Service Type}/{Entity}/. They contain
no behavior — only properties. Domain comments on properties capture validation intent
(e.g., nullable rules, email format, phone format).
The LegacyUser class resides under Models/Foundations/LegacyUsers/.
The Person class resides under Models/Foundations/Persons/.
Exception models live in Models/{Service Type}/{Entity}/Exceptions/ and form a two-tier exception
hierarchy per The Standard.
The LegacyUser class resides under Models/Foundations/LegacyUsers/Exceptions/.
The Person class resides under Models/Foundations/Persons/Exceptions/.
The inner/outer exception hierarchy varies by the type of broker the Foundation Service
consumes. Storage-based services (e.g., LegacyUserService) handle SQL/EF exceptions, while
API-based services (e.g., PersonService) handle RESTFulSense HTTP exceptions.
| Exception | Purpose |
|---|---|
NullLegacyUserException | Entity is null |
InvalidLegacyUserException | One or more property-level validation fails |
AlreadyExistsLegacyUserException | Duplicate key detected |
FailedStorageLegacyUserDependencyException | SQL / storage-level failure |
FailedLegacyUserServiceException | Unexpected runtime failure |
| Exception | Category | Wrapped Inner(s) |
|---|---|---|
LegacyUserValidationException | Validation | NullLegacyUserException, InvalidLegacyUserException |
LegacyUserDependencyException | Dependency | FailedStorageLegacyUserDependencyException |
LegacyUserDependencyValidationException | DependencyValidation | AlreadyExistsLegacyUserException, InvalidLegacyUserException (from DbUpdateException) |
LegacyUserServiceException | Service | FailedLegacyUserServiceException |
| Exception | Purpose |
|---|---|
NullPersonException | Entity is null |
InvalidPersonException | One or more property-level validation fails, or BadRequest from API |
AlreadyExistsPersonException | Conflict (409) from API |
FailedPersonDependencyException | HTTP-level failure (any HTTP error or HttpRequestException) |
FailedPersonServiceException | Unexpected runtime failure |
| Exception | Category | Wrapped Inner(s) |
|---|---|---|
PersonValidationException | Validation | NullPersonException, InvalidPersonException |
PersonDependencyValidationException | DependencyValidation | InvalidPersonException (from BadRequest), AlreadyExistsPersonException (from Conflict) |
PersonDependencyException | Dependency (Critical) | FailedPersonDependencyException (from Unauthorized, Forbidden, NotFound, UrlNotFound, HttpRequestException) |
PersonDependencyException | Dependency (Non-Critical) | FailedPersonDependencyException (from InternalServerError, ServiceUnavailable) |
PersonServiceException | Service | FailedPersonServiceException |
All exceptions derive from Xeption (from the Xeption NuGet package), which provides
UpsertDataList and ThrowIfContainsErrors for aggregated validation data.
A Foundation Service sits directly on top of Brokers and is the only consumer of them.
The service is split into three partial files:
| Partial file | Concern |
|---|---|
{Entity}Service.cs | Constructor, DI fields, public business logic |
{Entity}Service.Validations.cs | All Validate* and IsInvalid* methods |
{Entity}Service.Exceptions.cs | TryCatch delegate pattern, CreateAndLog* helpers |
A Foundation Service depends only on Brokers — never on other services.
Storage-based service (LegacyUserService):
public partial class LegacyUserService : ILegacyUserService
{
private readonly IStorageBroker storageBroker;
private readonly ILoggingBroker loggingBroker;
public LegacyUserService(
IStorageBroker storageBroker,
ILoggingBroker loggingBroker) { ... }
}
API-based service (PersonService):
public partial class PersonService : IPersonService
{
private readonly IModernApiBroker modernApiBroker;
private readonly ILoggingBroker loggingBroker;
public PersonService(
IModernApiBroker modernApiBroker,
ILoggingBroker loggingBroker) { ... }
}
Every public method delegates to a TryCatch wrapper that catches, wraps, logs, and
re-throws categorised exceptions:
Storage-based:
public ValueTask<LegacyUser> AddLegacyUserAsync(LegacyUser legacyUser) =>
TryCatch(async () =>
{
ValidateLegacyUser(legacyUser);
return await this.storageBroker.InsertLegacyUserAsync(legacyUser);
});
API-based:
public ValueTask<Person> AddPersonAsync(Person person) =>
TryCatch(async () =>
{
ValidatePerson(person);
return await this.modernApiBroker.PostPersonAsync(person);
});
Validations use a dynamic rule + aggregate pattern:
Validate(
(IsInvalid(legacyUser.Id), nameof(LegacyUser.Id)),
(IsInvalid(legacyUser.UserPK), nameof(LegacyUser.UserPK)),
(IsInvalid(legacyUser.UserName), nameof(LegacyUser.UserName)),
...);
Each IsInvalid overload returns an anonymous object with Condition (bool) and Message (string).
The Validate method aggregates all failures into a single InvalidLegacyUserException via
UpsertDataList and calls ThrowIfContainsErrors().
Custom validators exist for domain-specific rules:
| Validator | Rule |
|---|---|
IsInvalid(Guid) | Must not be Guid.Empty |
IsInvalid(int) | Must not be default (0) |
IsInvalid(string) | Must not be null/empty/whitespace |
IsInvalidLob(int) | Must be greater than 0 |
IsInvalidEmail | Regex-based email format check |
The TryCatch method in each {Entity}Service.Exceptions.cs implements The Standard's
exception mapping pattern. The specific catch blocks differ based on the broker type
the service consumes.
Native / External Exception → Inner (Local) Exception → Outer (Categorical) Exception
──────────────────────────────────────────────────────────────────────────────────────────────────
(null input) → NullLegacyUserException → LegacyUserValidationException
(validation rules fail) → InvalidLegacyUserException → LegacyUserValidationException
SqlException → FailedStorage...Exception → LegacyUserDependencyException (Critical)
DuplicateKeyException → AlreadyExists...Exception → LegacyUserDependencyValidationException
DbUpdateException → InvalidLegacy...Exception → LegacyUserDependencyValidationException
Exception (catch-all) → FailedService...Exception → LegacyUserServiceException
For services that call external APIs through RESTFulSense, the exception mapping covers
all HTTP response exceptions. The ordering of catch blocks in TryCatch matters — more
specific exceptions must appear before their base classes.
Native / External Exception → Inner (Local) Exception → Outer (Categorical) Exception Log Level
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
(null input) → NullPersonException → PersonValidationException LogError
(validation rules fail) → InvalidPersonException → PersonValidationException LogError
HttpResponseBadRequestException → InvalidPersonException → PersonDependencyValidationException LogError
HttpResponseConflictException → AlreadyExistsPersonException → PersonDependencyValidationException LogError
HttpResponseUnauthorizedException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseForbiddenException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseNotFoundException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseUrlNotFoundException → FailedPersonDependencyException → PersonDependencyException LogCritical
HttpResponseInternalServerErrorException → FailedPersonDependencyException → PersonDependencyException LogError
HttpResponseServiceUnavailableException → FailedPersonDependencyException → PersonDependencyException LogError
HttpRequestException → FailedPersonDependencyException → PersonDependencyException LogCritical
Exception (catch-all) → FailedPersonServiceException → PersonServiceException LogError
Rule — Critical vs Non-Critical Dependency: Exceptions that indicate the API endpoint is unreachable or inaccessible (
Unauthorized,Forbidden,NotFound,UrlNotFound,HttpRequestException) are logged atLogCriticalAsyncbecause they signal configuration or infrastructure failures. Server-side errors (InternalServerError,ServiceUnavailable) are logged atLogErrorAsyncbecause they may be transient.
Each CreateAndLog* helper:
ILoggingBroker at the appropriate level (LogErrorAsync or LogCriticalAsync).TryCatch can throw it.All wiring is done in Program.cs following The Standard's explicit registration style:
builder.Services.AddDbContext<StorageBroker>();
builder.Services.AddTransient<IStorageBroker, StorageBroker>();
builder.Services.AddTransient<IModernApiBroker, ModernApiBroker>();
builder.Services.AddTransient<ILoggingBroker, LoggingBroker>();
builder.Services.AddTransient<ILegacyUserService, LegacyUserService>();
builder.Services.AddTransient<IPersonService, PersonService>();
Brokers and Foundation Services are registered as Transient.
| Element | Pattern | Example |
|---|---|---|
| Broker interface | I{Resource}Broker | IStorageBroker, IModernApiBroker, ILoggingBroker |
| Broker class | {Resource}Broker | StorageBroker, ModernApiBroker, LoggingBroker |
| Broker method | {Action}{Entity}Async | InsertLegacyUserAsync, PostPersonAsync |
| Service interface | I{Entity}Service | ILegacyUserService |
| Service class | {Entity}Service | LegacyUserService |
| Service method | Add{Entity}Async | AddLegacyUserAsync |
| Inner exception | {Adjective}{Entity}Exception | NullLegacyUserException |
| Outer exception | {Entity}{Category}Exception | LegacyUserValidationException |
| Test class | {Entity}ServiceTests | LegacyUserServiceTests |
| Test method | Should{Action}Async / ShouldThrow{Exception}On{Action}… | ShouldAddLegacyUserAsync |