Перейти до основного вмісту

Загальна архітектура

Ця сторінка — карта, до якої ви будете постійно повертатися. Вона не заглиблюється в жодну конкретну підсистему — це роблять посібники «Створення...» та розділ Довідник — але вона повинна дозволити вам розмістити будь-який файл, на який ви дивитесь, у правильному шарі.

Структура вашої власної конфігурації

Саме це dotnet new kandra-config -n Acme створює у вашому репозиторії — 11 проєктів, усі з префіксом назви вашої конфігурації, і більше нічого:

Acme.slnx
src/
Acme.Domain # конкретні сутності *Base (Entities/Dictionaries, /Documents, /Registers, ...)
Acme.Forms # конкретні *Dto + *Validator (спільні клієнт+сервер)
Acme.Application # конкретні *Behavior + профілі AutoMapper, згенерована DI-конфігурація
Acme.Persistence # згенеровані EntityConfigurations, репозиторії
Acme.Persistence.Databases # DbContext(и) + міграції для кожного провайдера (лише для обраних)
Acme.WebApi # запускний хост — сюди потрапляють згенеровані контролери
Acme.Client.MudBlazor # робочий клієнт Blazor WebAssembly — 100% згенеровані сторінки
Acme.ClientLib.Common # написані вручну інтерфейси Refit + клієнтські сервіси
Acme.Enums
Acme.Localization
Acme.Tests

Спочатку все порожньо (без Довідників/Документів/Звітів/Регістрів/Обробників даних) і зростає по одній сутності за раз через вбудовані .claude/skills/ — дивіться Як почати.

Найважливіше правило в цій структурі: Acme.Forms ніколи не повинен посилатися на Acme.Application/Acme.Domain/Acme.Persistence. Клієнт Blazor WebAssembly бачить лише Forms (плюс Enums/Localization); сервер також посилається на Forms, оскільки мапери та поведінки потребують і сутності *Base, і *Dto. Порушення цієї межі ламає збірку клієнта, оскільки той намагається підтягнути EF Core.

На що ви посилаєтесь, а не що переглядаєте

Kandra.* використовується виключно через NuGet PackageReference — у вашому репозиторії немає жодного вихідного коду платформи, якщо ви окремо не отримали доступ до монорепозиторію команди платформи для довідки (дивіться Як почати). Таблиця нижче показує, де насправді живе логіка певної концепції, навіть якщо ви не можете туди cd:

ПакетЗа що відповідає
Kandra.DomainБазові класи сутностей, механізм регістрів, контракти Identity — без EF
Kandra.Application(.Abstractions)CRUD-сервіси, контракти behavior/validator/mapper, нумерація
Kandra.Persistence(.Abstractions)Інтеграція EF Core, читачі/писачі регістрів, підсистема обліку
Kandra.Api.LibНалаштування ASP.NET Core (AddKandra/UseKandra), базові класи контролерів
Kandra.AttributesСловник метаданих, який читають генератори
Kandra.ClientLib.Common/.MudBlazorКлієнтська DI-обв'язка
Kandra.Generators.*Генератори коду Roslyn (виконуються під час вашої збірки, з NuGet-пакета)
Kandra.PrintingЕкспорт у PDF/CSV, форматування значень з урахуванням часового поясу

$(KandraCoreVersion) у вашому Directory.Build.props фіксує, яку опубліковану збірку цих пакетів ви використовуєте — рядок версії, який підтримується вручну й оновлюється після перевірки, що новіша версія рушія справді працює з вашою конфігурацією.

Конвеєр генерації, від початку до кінця

Ви пишете: сутність *Base + *Dto (з атрибутами) + *Behavior + *Validator


Kandra.Generators.* читають атрибути Dto/сутності під час компіляції

┌───────────┼──────────────────┬────────────────────┬──────────────┐
▼ ▼ ▼ ▼ ▼
Конфігурація CRUD-контролер Клієнтський DI (клієнт DI-реєстрація Згенеровані сторінки
EF Core (Kandra.Generators. Refit + обгортка (ConfigureServices Blazor UI
(Generated Api) → WebApi сервісу) → .cs) (емітери Document/
.Net/) ClientLib Report)

Тут немає жодного проходу з рефлексією під час виконання — усе відбувається під час dotnet build, а вивід — це звичайний закомічений C#/Razor у папці Generated.Net/ кожного проєкту. Читайте його, коли хочете точно знати, що створює атрибут; ніколи не редагуйте вручну.

Потік під час запиту (проведення документа як показовий випадок)

  1. Клієнт Blazor надсилає POST-запит на ендпоінт згенерованого контролера.
  2. Згенерований контролер викликає CRUD-сервіс прикладного шару, який вирішує *Behavior сутності.
  3. OnBeforeSaveAsync / OnSaveAsync виконуються навколо збереження EF Core.
  4. Саме під час проведення виконується OnSubmitAsync з параметром ISubmitScope — це єдиний легальний шлях до ISubmitScope.GetWriter<TWriter>() (писачі регістрів) та IPostingService (проведення в план рахунків), обидва — keyed DI-сервіси, недосяжні через звичайне впровадження залежностей у конструктор. Це навмисна точка звуження: ніщо поза проведенням не може записати регістр чи здійснити проведення.
  5. Конвертація часового поясу відбувається на виході: сервер зберігає/віддає DateTime виключно в UTC; зареєстрований JsonConverter<DateTime> конвертує кожне поле DTO, що передається по мережі, для відображення, а PrintValueFormatter з Kandra.Printing конвертує на стороні сервера для експорту в PDF/CSV. Клієнт нативно визначає свій IANA часовий пояс і надсилає його як заголовок X-Time-Zone з кожним запитом.

Дві суміжні з обліком підсистеми, навмисно не об'єднані

  • Механізм регістрів (регістри Kandra.Domain/Kandra.Persistence) — загальне аналітичне сховище: залишки, обороти, інформаційні записи. Будь-який документ може записати будь-який потрібний йому регістр.
  • Підсистема плану рахунків / проведень (Kandra.Persistence/Accounting, таблиці Acc_) — спеціально побудований обліковий рушій у стилі подвійного запису з власними кодами рахунків, слотами вимірювань (субконто) та пакетами проведень.

Вони виглядають подібно (обидва — «документи записують структуровані записи під час проведення»), але побудовані незалежно навмисно — дивіться Механізм регістрів та План рахунків / проведення, чому саме так, і не намагайтесь реалізувати одне через інше.

Ідентифікація та локалізація, коротко

  • Ідентифікація узагальнена за <TUser, TRole> на рівні платформи, закрита конкретними типами для кожної конфігурації. Два шляхи JWT: користувачі-люди та API-ключі для не-людських викликів.
  • Локалізація — це ланцюжок модулів, що накладаються (LocalizationChain) — модуль, зареєстрований останнім, перемагає для кожного ключа, з переходом до попередніх модулів. services.AddLocalization() має викликатися перед AddLocalizationModule().

Дивіться Ідентифікація та автентифікація та Локалізація для повної картини кожної з них.

Куди йти далі

Оберіть вид сутності, яку ви збираєтесь створювати: Довідник, Регістр, Документ, Звіт, Обробник даних або Константа — кожен посібник передбачає, що ви прочитали цю сторінку.