-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsend_to_ai_chat.txt
More file actions
261 lines (114 loc) · 436 KB
/
send_to_ai_chat.txt
File metadata and controls
261 lines (114 loc) · 436 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# swift
Начинай с понимания домена и формулировки бизнес-целей, а не с выбора фреймворка или библиотек Swift. ,Собирай и документируй функциональные и нефункциональные требования; фиксируй их рядом с кодом (docc, README, ADR) в одном репозитории.,Строй общий глоссарий и ubiquitous language вместе с бизнесом; отражай его в названиях типов, методов и модулей Swift.,Явно опиши границы системы и контексты взаимодействия (модульные фреймворки, отдельные таргеты, пакеты Swift Package Manager).,Проектируй архитектуру от домена и use case’ов, а не от Vapor, SwiftUI, UIKit или CoreData.,Разделяй бизнес-логику и инфраструктуру как концептуально, так и в коде: отдельные модули/targets для Domain, Application, Infrastructure, UI.,Строй систему вокруг слоёв use-case / application: отдельные типы (interactors/use cases) в Swift, отвечающие за сценарии, а не контроллеры/вью-модели.,Избегай прямых зависимостей домена от баз данных, сети и UI: в домене только протоколы и чистые типы Swift.,Сначала определяй доменную модель, сущности и их инварианты в виде структур и классов Swift, с инвариантами, проверяемыми в инициализаторах и методах.,Различай сущности (с идентичностью), value objects (без идентичности) и их ответственность; реализуй их в виде `struct` и `class`, подбирая семантику по смыслу, а не по привычке.,Определяй агрегаты и их границы; гарантируй непротиворечивость инвариантов в пределах одного агрегата.,Проектируй доменные события и их реакции; отрази их в виде типов событий и обработчиков (listeners/handlers) на уровне домена или application.,Определи интерфейсы репозиториев и доменных сервисов как Swift-протоколы; реализации оставь для инфраструктурного слоя.,Применяй Ports-and-Adapters (Hexagonal Architecture), чтобы изолировать инфраструктуру: домен задаёт протоколы-порты, инфраструктура реализует адаптеры.,Обеспечь, чтобы каждая часть системы имела одну чёткую ответственность.,Строго следуй SRP: один модуль / один тип — одна причина для изменения.,Следуй OCP: расширяй поведение через новые реализации протоколов и новые типы, не переписывая стабильный код.,Следуй LSP: подтипы (классы, generic-параметры) не должны нарушать ожидания клиентов; не ломай контракты базовых протоколов/классов.,Следуй ISP: разбивай "толстые" протоколы на небольшие специализированные; не заставляй реализации поддерживать лишние методы.,Следуй DIP: высокоуровневый код зависит от абстракций (протоколов), а не от конкретных классов и инфраструктурных деталей.,Явно объявляй протоколы для всех внешних зависимостей: сеть, БД, файловая система, очереди, аналитика, пуши.,Внедряй зависимости через инициализаторы, фабрики или DI-контейнер (собственный лёгкий контейнер, Swift Concurrency, property wrappers), избегай скрытого синглтона `shared`.,Избегай сервис-локаторов и скрытых глобальных синглтонов; любое использование `shared`/глобалов должно быть осознанным и изолированным.,Проектируй модули с минимальными и понятными публичными API; экспортируй только то, что нужно использовать снаружи.,Держи зависимости между модулями однонаправленными и как можно более слабыми: через протоколы и абстракции, а не через прямые импорт-циклы.,Предпочитай композицию наследованию при расширении поведения: декораторы, композиция протоколов, wrapper-типы вместо глубокой иерархии классов.,Используй наследование только для реальных иерархий и полиморфизма (например, базовые UI-компоненты, системные классы), а не для "удобства".,Избегай god objects и "менеджеров всего" (`SomethingManager`, `SomethingService` без чётких границ ответственности).,Не допускай утечки деталей реализации через публичные протоколы и API: возвращай абстракции, а не конкретные инфраструктурные типы.,Делай модули маленькими и сфокусированными; при усложнении класса раскладывай его на более простые типы.,Явно разделяй слои: Domain, Application, Infrastructure, Presentation (UI) — разными targets / SwiftPM-пакетами / группами.,Не допускай зависимостей от внутренних слоёв к внешним: Domain не знает про Infrastructure и UI, Application не зависит напрямую от UI.,Пусть UI (UIKit/SwiftUI) зависит от слоя Application (use-case, view model), а не от домена напрямую.,Пусть инфраструктура (Vapor, URLSession, CoreData, GRDB, Redis, Kafka и т.п.) реализует доменные порты-протоколы, а не диктует форму доменной модели.,Вынеси всю конфигурацию (URL, ключи, флаги) из кода: используй `.xcconfig`, environment variables, plist, конфиг-файлы и секрет-хранилища.,Никогда не хардкодь чувствительные данные (ключи, токены, пароли) в репозитории; не клади их в Info.plist без шифрования и обфускации.,Не смешивай бизнес-логику с IO-операциями: сеть, файлы и БД держи в адаптерах/репозиториях инфраструктурного слоя.,Старайся держать как можно больше функций чистыми (без побочных эффектов): используй `struct` и неизменяемость по умолчанию.,Там, где это даёт ценность, разделяй операции чтения и записи (CQRS-стиль): отдельные query-сервисы и отдельные командные use cases.,Используй простые оптимизированные модели для чтения (DTO, projections), и строгие доменные модели для записи и бизнес-инвариантов.,Используй простые структуры данных / DTO для передачи данных между слоями (например, `struct UserDTO` для сети и базы).,Не протаскивай доменные сущности в инфраструктуру и транспортные слои; маппинг между DTO и доменом делай на уровне application.,Проектируй API-контракты (REST/GraphQL/gRPC/WebSocket) стабильными и обратно совместимыми; описывай схемы явно (OpenAPI/AsyncAPI/Protobuf).,Версионируй внешние API и события; добавляй новые поля без ломания старых клиентов.,Ясно описывай схемы сообщений и форматы данных: JSON, Protobuf, Avro, собственные бинарные протоколы.,Обрабатывай ошибки и исключения явно; продумывай коды ответов и доменные ошибки как часть контракта.,Различай бизнес-ошибки (валидация, ограничения домена) и технические ошибки (таймаут, сетевая ошибка, сбой БД).,Никогда не глуши ошибки; логируй и обрабатывай их соответствующим образом, не оставляя пустые `catch {}`.,Валидируй данные на границах системы (transport layer, DTO-декодеры) и внутри домена; не полагайся только на UI.,Пусть доменные инварианты соблюдаются самим доменом — через инициализаторы, методы и типы-обёртки, а не лишь проверками в UI.,Думай о безопасности с самого начала: аутентификация, авторизация, аудит, конфиденциальность данных (в т.ч. на клиенте).,Минимизируй площадь атаки: не раскрывай лишних деталей в логах, ошибках и API; ограничивай доступ к отладочной информации.,Логируй ключевые события на границах системы и в критических точках домена; используй структурированное логирование (метки, поля, correlationId).,Добавляй метрики и трассировку (tracing) для наблюдаемости; интегрируйся с прометей-подобными системами и APM, если это серверный Swift.,Проектируй систему так, чтобы её можно было мониторить и настраивать алерты (например, по задержкам, частоте ошибок, ресурсам).,Выбирай протоколы и форматы коммуникации исходя из требований (latency, объём, доступность), а не моды: HTTP/REST, gRPC, WebSocket и т.д.,Проектируй систему с учётом сетевых ошибок, задержек и временной недоступности сервисов; на стороне клиента и сервера.,Применяй ретраи, таймауты и circuit breaker там, где это оправдано; не забывай об ограничениях по ресурсам на iOS/macOS.,Думай об идемпотентности операций под ретраями: используйте идемпотентные ключи, уникальные идентификаторы команд.,Обеспечь корректные миграции данных и эволюцию схемы; используй миграции БД и совместимые изменения моделей.,Заранее продумай стратегию миграции БД и обратной совместимости (поддержка старых клиентов, старых схем).,Используй feature flags для безопасного включения новых возможностей и A/B-тестов; не выкатывай большие изменения "одним куском".,Проектируй код так, чтобы его можно было выкатывать инкрементально и безопасно откатывать; избегай жёстких миграций на стороне клиента.,Продумывай масштабирование по оси чтения и записи; кеширование, репликация, шардирование, horizont scaling для серверного Swift.,Начинай с простой, масштабируемой монолитной архитектуры (один сервис, один backend), даже если пишешь на Vapor, и только затем дроби.,Дроби систему на сервисы только при реальной необходимости (организационной или технической), а не "потому что микросервисы модно".,При использовании микросервисов явно определяй bounded contexts и владение данными; избегай общих таблиц/схем между сервисами.,Минимизируй синхронные зависимости между сервисами; используй асинхронную коммуникацию и события там, где это оправдано.,Избегай распределённых транзакций и бухгалтерских компенсирующих операций, если можно обойтись проще; применяй их осознанно.,Проектируй соглашения об идемпотентности и согласованности данных при интеграции сервисов (eventual consistency, outbox pattern).,Думай наперёд об эволюции схем для сообщений и контрактов (добавление полей, изменение типов).,Пиши код, который легко тестировать: минимизируй скрытые зависимости, используй протоколы и чистые функции.,Разделяй бизнес-логику и фреймворки (UIKit/SwiftUI, Vapor и т.д.), чтобы домен можно было тестировать юнит-тестами без поднятия среды.,Покрывай домен модульными тестами; тестируй интеграцию на уровне адаптеров и границ (репозитории, HTTP-клиенты, API).,Используй тест-дублёры (mocks, fakes, stubs) для внешних зависимостей, реализуя соответствующие протоколы.,Автоматизируй ключевые end-to-end сценарии; подключай UI-тесты и интеграционные тесты к CI.,Держи тестовый набор быстрым и надёжным; избегай непостоянных/нестабильных тестов (flaky) и избыточной зависимости от сети.,Интегрируй тесты в CI/CD-пайплайн; не допускай ручных шагов при проверке критических частей.,Организуй структуру папок и модулей так, чтобы она отражала архитектуру (Domain, Application, Infrastructure, Presentation), а не "как получится" в Xcode.,Называй пакеты, модули и пространства имён (SwiftPM targets, frameworks) в соответствии с их ролью и слоем.,Используй читаемые, осмысленные имена типов, методов и переменных; избегай абстрактных `Manager`, `Helper`, `Utils` без контекста.,Документируй публичные контракты и ключевую бизнес-логику (doc comments, docc, README); не перегружай код очевидными комментариями.,По возможности улучшай структуру и нейминг вместо того, чтобы объяснять "плохой" код комментариями.,Относись к code review как к обязательному шагу; обсуждай архитектурные решения и компромиссы с командой.,Фиксируй архитектурные решения в коротких ADR (Architecture Decision Records); держи их рядом с кодом.,Регулярно пересматривай архитектуру в свете новых требований, но избегай полных переписываний без очень веских причин.,Отличай временные решения от долгосрочных; помечай технический долг и планируй, как его погашать.,Следуй единому стилю кода и форматированию; используй линтеры (SwiftLint) и автоформатеры (SwiftFormat).,Не занимайся преждевременной оптимизацией; сначала сделай код ясным и корректным, затем измеряй и оптимизируй.,При оптимизации используй профилирование (Instruments, логирование) для поиска реальных узких мест.,Избегай "магии" и чрезмерно хитрых решений; предпочитай простоту и явность, даже если это немного больше кода.,Осознанно ограничивай стек технологий и фреймворков; не тащи лишний пакет в SPM/Podfile без ясной выгоды.,Минимизируй единичные точки отказа; проектируй систему так, чтобы отдельные компоненты можно было разворачивать, обновлять и заменять независимо.,Обеспечивай обратную совместимость при эволюции протоколов и моделей (версии API, graceful degradation).,Проектируй систему устойчивой к частичным отказам; учитывай сценарии, когда упала только часть инфраструктуры.,Проектируй graceful degradation и fallback-сценарии: частичная функциональность лучше, чем полный отказ.,Думай об эксплуатационных аспектах: логирование, мониторинг, алерты, дашборды; не только о коде.,Автоматизируй сборку, тесты, деплой и миграции (скрипты, CI/CD pipelines, fastlane для iOS, shell-скрипты для серверного Swift).,Проектируй архитектуру так, чтобы новые Swift-разработчики могли быстро в неё въехать: понятные имена, структура, стартовое README.,Держи документацию в актуальном состоянии; удаляй и обновляй устаревшие диаграммы и описания.,Упрощай архитектуру, когда есть возможность, вместо бессмысленного усложнения.,Постоянно балансируй между идеальной чистотой и прагматикой; где возможно — поддерживай строгие границы, явные контракты и чёткое разделение ответственности между слоями, модулями и командами.
Start with understanding the domain and defining business goals, not with choosing a Swift framework or library.,Gather and document functional and non-functional requirements; store them close to the code (docc, README, ADR) in the same repository.,Build a shared glossary and ubiquitous language together with the business; reflect it in the names of Swift types, methods, and modules.,Explicitly describe system boundaries and interaction contexts (modular frameworks, separate targets, Swift Package Manager packages).,Design the architecture from the domain and use cases, not from Vapor, SwiftUI, UIKit, or CoreData.,Separate business logic and infrastructure both conceptually and in code: separate modules/targets for Domain, Application, Infrastructure, UI.,Build the system around use-case/application layers: dedicated types (interactors/use cases) in Swift responsible for scenarios, not controllers/view models.,Avoid direct dependencies from the domain to databases, network, and UI: the domain should contain only protocols and pure Swift types.,First define the domain model, entities, and their invariants as Swift structs and classes, enforcing invariants in initializers and methods.,Distinguish entities (with identity), value objects (without identity), and their responsibilities; implement them as `struct` and `class` based on semantics, not habit.,Define aggregates and their boundaries; ensure invariants stay consistent within a single aggregate.,Design domain events and their reactions; represent them as event types and handlers/listeners in the domain or application layer.,Define interfaces of repositories and domain services as Swift protocols; keep their implementations in the infrastructure layer.,Apply Ports-and-Adapters (Hexagonal Architecture) to isolate infrastructure: the domain defines ports (protocols), infrastructure implements adapters.,Ensure each part of the system has one clear responsibility.,Strictly follow SRP: one module / one type — one reason to change.,Follow OCP: extend behavior via new protocol implementations and new types, instead of modifying stable code.,Follow LSP: subtypes (classes, generic parameters) must not violate client expectations; do not break contracts of base protocols/classes.,Follow ISP: split “fat” protocols into small, focused ones; do not force implementations to support unnecessary methods.,Follow DIP: high-level code depends on abstractions (protocols), not on concrete classes and infrastructure details.,Explicitly declare protocols for all external dependencies: network, DB, file system, queues, analytics, push notifications.,Inject dependencies via initializers, factories, or a DI container (lightweight custom container, Swift Concurrency, property wrappers); avoid hidden `shared` singletons.,Avoid service locators and hidden global singletons; any use of `shared`/globals must be deliberate and isolated.,Design modules with minimal and understandable public APIs; export only what must be used from the outside.,Keep dependencies between modules one-directional and as weak as possible: via protocols and abstractions, not direct cyclic imports.,Prefer composition over inheritance when extending behavior: decorators, protocol composition, wrapper types instead of deep class hierarchies.,Use inheritance only for true hierarchies and polymorphism (e.g., base UI components, system classes), not just for “convenience”.,Avoid god objects and “manager of everything” classes (`SomethingManager`, `SomethingService` with vague responsibilities).,Do not let implementation details leak through public protocols and APIs: expose abstractions rather than concrete infrastructure types.,Keep modules small and focused; when a class becomes too complex, split it into simpler types.,Explicitly separate layers: Domain, Application, Infrastructure, Presentation (UI) as different targets / SwiftPM packages / groups.,Do not allow dependencies from inner layers to outer layers: Domain must not know about Infrastructure or UI, Application must not depend directly on UI.,Let UI (UIKit/SwiftUI) depend on the Application layer (use cases, view models), not on the Domain directly.,Let Infrastructure (Vapor, URLSession, CoreData, GRDB, Redis, Kafka, etc.) implement domain ports/protocols instead of shaping the domain model.,Move all configuration (URLs, keys, flags) out of code: use `.xcconfig`, environment variables, plist, config files, and secret stores.,Never hardcode sensitive data (keys, tokens, passwords) in the repository; do not store them in Info.plist without proper protection/obfuscation.,Do not mix business logic with IO operations: keep network, file, and database access in adapters/repositories of the infrastructure layer.,Keep as many functions as possible pure (without side effects); use `struct` and immutability by default.,Where it brings value, separate read and write operations (CQRS-style): separate query services from command use cases.,Use simple optimized models for reads (DTOs, projections) and strict domain models for writes and domain invariants.,Use simple data structures / DTOs to transfer data between layers (e.g., `struct UserDTO` for network and DB).,Do not drag domain entities into infrastructure and transport layers; perform mapping between DTOs and domain models in the application layer.,Design API contracts (REST/GraphQL/gRPC/WebSocket) to be stable and backward compatible; describe schemas explicitly (OpenAPI/AsyncAPI/Protobuf).,Version external APIs and events; add new fields without breaking existing clients.,Describe message schemas and data formats clearly: JSON, Protobuf, Avro, custom binary protocols.,Handle errors and exceptions explicitly; design error types and response codes as part of the contract.,Distinguish business errors (validation, domain rules) from technical failures (timeout, network error, DB failure).,Never swallow exceptions; log and handle them appropriately, avoid empty `catch {}` blocks.,Validate data both at system boundaries (transport layer, DTO decoders) and inside the domain; do not rely solely on UI checks.,Let domain invariants be enforced by the domain itself via initializers, methods, and wrapper types, not only by UI validation.,Design security from the beginning: authentication, authorization, auditing, data privacy (including on clients).,Minimize the attack surface: do not expose unnecessary details in logs, errors, and APIs; restrict access to debug information.,Log key events at system boundaries and in critical domain points; use structured logging (fields, labels, correlationId).,Add metrics and tracing for observability; integrate with Prometheus-like systems and APM for server-side Swift where relevant.,Design the system so it can be monitored and alerted on (latency, error rates, resource usage).,Choose communication protocols and formats based on requirements (latency, volume, availability), not fashion: HTTP/REST, gRPC, WebSocket, etc.,Design the system with network errors, latency, and temporary service unavailability in mind, both on client and server.,Apply retries, timeouts, and circuit breakers where appropriate; respect resource constraints on iOS/macOS.,Think about idempotency of operations under retries: use idempotency keys, unique command identifiers.,Ensure correct data migration and schema evolution; use DB migrations and compatible model changes.,Plan a database migration and backward-compatibility strategy in advance (support for old clients and old schemas).,Use feature flags to enable new capabilities safely and to run A/B tests; avoid rolling out massive changes in a single step.,Design code so it can be rolled out incrementally and rolled back safely; avoid hard, irreversible client-side migrations.,Think about scaling along the read axis and the write axis; caching, replication, sharding, horizontal scaling for server-side Swift.,Start with a simple, scalable monolith architecture (one service, one backend) even with Vapor, and only then split it.,Split the system into services only when there is real need (organizational or technical), not just “because microservices are trendy”.,When using microservices, clearly define bounded contexts and data ownership; avoid shared tables/schemas between services.,Minimize synchronous inter-service dependencies; use asynchronous communication and events when justified.,Avoid distributed transactions and compensating operations unless necessary; use them consciously.,Design conventions for idempotency and data consistency when integrating services (eventual consistency, outbox pattern).,Think ahead about schema evolution for messages and contracts (field additions, type changes).,Write code so that it is easy to test: minimize hidden dependencies, use protocols and pure functions.,Separate business logic from frameworks (UIKit/SwiftUI, Vapor, etc.) so you can unit test the domain without a full runtime environment.,Cover the domain with unit tests; test integration at the level of adapters and boundaries (repositories, HTTP clients, APIs).,Use test doubles (mocks, fakes, stubs) for external dependencies by implementing the appropriate protocols.,Automate key end-to-end scenarios; add UI tests and integration tests to CI.,Keep the test suite fast and reliable; avoid flaky tests and excessive real network usage.,Integrate tests into the CI/CD pipeline; avoid manual steps for verifying critical parts.,Design folder and module structure to reflect the architecture (Domain, Application, Infrastructure, Presentation), not accidental Xcode layout.,Name packages, modules, and namespaces (SwiftPM targets, frameworks) according to their roles and layers.,Use readable, meaningful names for types, methods, and variables; avoid abstract `Manager`, `Helper`, `Utils` without context.,Document public contracts and key business logic (doc comments, docc, README); avoid excessive comments on obvious code.,Improve structure and naming instead of relying on comments to explain poor code.,Treat code review as a mandatory step; discuss architectural decisions and trade-offs with the team.,Record architectural decisions in short ADRs (Architecture Decision Records); keep them close to the code.,Regularly revisit the architecture in light of new requirements but avoid full rewrites without very strong reasons.,Distinguish temporary solutions from long-term ones; mark technical debt and plan how to pay it off.,Follow a consistent code style and formatting; automate linters (SwiftLint) and formatters (SwiftFormat).,Do not optimize prematurely; first make the code clear and correct, then measure and optimize.,Use profiling (Instruments, logging) to find real bottlenecks when optimizing.,Avoid “magic” and overly clever solutions; prefer simplicity and explicitness, even if it means more code.,Deliberately limit the technology and framework stack; do not add new SPM/CocoaPods/Carthage dependencies without clear benefit.,Minimize single points of failure; design the system so individual components can be deployed, updated, and replaced independently.,Ensure backward compatibility when evolving protocols and models (API versions, graceful degradation).,Make the system resilient to partial failures; consider scenarios where only part of the infrastructure is down.,Design graceful degradation and fallback scenarios: partial functionality is better than total failure.,Think about operability: logging, monitoring, alerts, dashboards—not only about code.,Automate build, tests, deployment, and migrations (scripts, CI/CD pipelines, fastlane for iOS, shell scripts for server-side Swift).,Design the architecture so that new Swift developers can onboard easily: clear naming, structure, and a good starting README.,Keep documentation up to date; remove or update outdated diagrams and descriptions.,Simplify the architecture whenever you can instead of complicating it.,Continuously balance between ideal cleanliness and pragmatism, and wherever possible maintain strict boundaries, explicit contracts, and clear separation of responsibilities between layers, modules, and teams.
#pgsql
проверить cache hit ratio и обеспечить >99% для горячих данных, увеличить память/эффективность кеша при снижении hit ratio, анализировать pg_stat_statements для поиска самых медленных запросов, оптимизировать запросы >10–100ms через индексы и переписывание логики, создавать индексы для часто используемых фильтров и JOIN-ов, проверять список неиспользуемых индексов и удалять лишние, контролировать рост индексов чтобы не замедлять INSERT/UPDATE, периодически анализировать запросы ORM и заменять их на raw SQL при проблемах, повторять аудит раз в 2–6 месяцев как обязательную рутину
начальная настройка базовых конфигураций PostgreSQL по проверенному чек-листу, проверка cache hit ratio по наиболее активным таблицам и обеспечение значения, максимально близкого к 100% за счёт подбора памяти/размера инстанса, включение и использование расширения pg_stat_statements, регулярный анализ самых медленных и «дорогих» запросов по среднему времени выполнения, оптимизация этих запросов (переписывание, добавление индексов под реальные фильтры и джойны), периодический поиск и удаление неиспользуемых индексов для уменьшения стоимости операций записи, повтор этих проверок каждые 2–6 месяцев и после существенных изменений схемы или нагрузки.
treat Postgres not as a “slow” database but as one that needs correct usage, use prepared statements in long-running services to parse and plan queries once and reuse them with different parameters, remember that prepared statements are scoped to a single session/connection and are often managed for you by drivers if you use the right API, benchmark and profile queries with EXPLAIN ANALYZE instead of guessing where the bottlenecks are, add appropriate indexes on columns used in WHERE/JOIN/ORDER BY so large tables avoid expensive sequential scans, keep in mind that indexes speed up reads but slow down writes because every insert and update must also update the index, partition very large tables by a suitable key (for example timestamp ranges or hashed IDs) so queries and maintenance operate on smaller chunks instead of the whole table, automate partition creation and cleanup with tools or extensions like pg_partman and pg_cron, use the COPY command to bulk-load large datasets from CSV or other supported formats instead of many individual INSERT statements, validate input data carefully because a single malformed row can make the whole COPY fail, scale read performance with read replicas so the primary handles writes while replicas serve most reads, use connection poolers or middleware such as pgbouncer or pgpool-II to route read and write traffic and hide replication complexity, continuously rerun realistic benchmarks after changes and iterate rather than expecting one magical tweak to fix all performance problems
====
design hardware carefully with fast CPUs (large L3 cache), SSD/NVMe and RAID1/10, lots of RAM and sufficient network throughput, on VMs choose suitable instance types, avoid noisy neighbors and consider dedicated hosts and NUMA pinning, configure virtual disks properly and pre-allocate where possible, tune the OS with tuned (DB-oriented profile), disable transparent huge pages, use performance CPU governor, reduce swapping, configure huge pages sized from shared_buffers and other memory use, choose XFS or ext4 with journaling and mount with noatime, tune core PostgreSQL settings: reasonable max_connections (plus a pooler), shared_buffers around 30–50% RAM, work_mem and maintenance_work_mem sized for workload, autovacuum_work_mem and effective_io_concurrency aligned with SSD/NVMe, tune WAL and checkpoint settings (wal_compression, wal_buffers, checkpoint_completion_target, checkpoint_timeout, max_wal_size) to smooth IO and recovery, tune planner hints (seq_page_cost, random_page_cost, cpu_tuple_cost, effective_cache_size) to match storage and cache behavior, set client and background options like idle_in_transaction_session_timeout and shared_preload_libraries (pg_stat_statements), configure autovacuum (autovacuum_max_workers, autovacuum_vacuum_cost_limit, log_autovacuum_min_duration) so it keeps up without overloading the system, enable logging for diagnostics (log_min_duration_statement, log_temp_files, log_checkpoints, extra timing in EPAS if available), continuously identify slow queries via logs, pg_stat_statements and tools like pgBadger, rewrite queries to avoid expressions on indexed columns, bad NOT IN patterns and unnecessary CTEs, use EXPLAIN and EXPLAIN ANALYZE (ideally in a transaction) and GUI tools to understand plans and fix bad estimates, external sorts, bad joins and missing indexes, use partitioning (range/list/hash, or automatic partitioning where available) on very large tables for easier maintenance, archiving and some query speedups, keep monitoring and re-tuning over time, upgrade regularly to new minor PostgreSQL versions and use detailed tuning guides as a deeper reference
### бизнес
начинаем не с регистрации ООО, логотипа и «идеального» названия, а с понимания проблемы, которую вы хотите решить, формулируем не идею-фичу («сделать приложение»), а *боль* и *ценность* («людям сложно X, я помогу им делать Y быстрее/дешевле/проще»), описываем целевого человека одним конкретным образом, а не «все пользователи интернета», проверяем, есть ли у этих людей реальная мотивация и готовность что-то менять в своём поведении ради вашей ценности,выносим идею из головы в реальность, не пишем сразу бизнес-план на 50 страниц, а делаем набор проверяемых гипотез, формулируем: кто клиент, какая его боль, какую выгоду он получает, как он узнает о продукте, за что и как платит, какие нам нужны ресурсы и партнёры, какую маржу мы хотим, для каждой гипотезы придумываем простой тест — разговор, опрос, прототип, лендинг, пилот,начинаем с общения, а не с кода, находим 10–20 потенциальных клиентов, проводим честные интервью о их текущих процессах и проблемах, задаём вопросы не «нравится ли вам моя идея», а «как вы решаете это сейчас, сколько времени/денег тратите, что вас больше всего бесит», фиксируем всё, не спорим и не продаём, ищем повторяющиеся паттерны, если люди не видят проблемы — честно признаём, что идея сыровата, и либо меняем сегмент, либо идею,формулируем ценностное предложение простым человеческим языком, одну чёткую фразу «для [кого], у которых [такая проблема], мы даём [такой результат] за [такие усилия/деньги] лучше чем [альтернатива]», избегаем абстракций и модных слов типа «инновационное решение на базе AI», говорим конкретикой: «сокращаем время X с 1 часа до 5 минут», «уменьшаем расходы на Y %»,делаем *минимально жизнеспособный продукт* (MVP), а не «идеальный продукт версии 1.0», вырезаем всё, без чего ценность не появится, оставляем только 1–2 ключевые штуки, которые реально решают боль, MVP не обязан быть приложением — это может быть форма, чат, таблица, ручной сервис, бот, что угодно, что позволяет проверить «люди в реальности готовы этим пользоваться и платить»,подбираем бизнес-модель, считаем простейшую юнит-экономику на салфетке, оцениваем, сколько стоит привлечь одного клиента, сколько он заплатит, какая переменная себестоимость, какая маржа, если модель не сходится даже в теории — честно признаём, что надо менять цену, формат или сегмент, не прячемся за «потом масштабируемся и всё станет хорошо»,строим первые каналы привлечения максимально простыми, не выжигаем бюджет на бренд-рекламу, начинаем с того, что можно сделать руками: личные контакты, профильные чаты и сообщества, холодные письма, партнёрства, нишевые соцсети, каждый канал рассматриваем как эксперимент, измеряем: сколько людей увидели, откликнулись, попробовали, заплатили,параллельно проектируем поведение бизнеса как систему, задаём ключевые метрики: число лидов, конверсии по этапам, retention, средний чек, LTV, unit-экономика, не тонем в сотнях цифр, а выбираем 3–5 критичных показателей и принимаем решения только на основе них, строим простейшие отчёты хотя бы в таблице,думаем о юридической и финансовой стороне, но не превращаем её в отговорку «пока не зарегистрирую фирму — ничего не могу делать», сначала валидируем идею на маленьких «пробных» транзакциях, как только становится понятно, что есть спрос — выбираем форму (ИП/компания и т.п.) в зависимости от страны, налогов и рисков, если вы младше 18 — обсуждаем формальную часть с родителями/опекунами, действуем в рамках закона, с самого начала стараемся вести учёт доходов и расходов, даже если это просто табличка,проектируем продукт и процессы так, чтобы их можно было *масштабировать*, не завязываемся на одном человеке, не строим систему, где все ключевые знания только в вашей голове, документируем простые процессы (как мы продаём, как обслуживаем, как считаем деньги), вводим минимальные стандарты качества, продумываем, что можно автоматизировать позже, но на старте делаем руками, чтобы понять, что вообще нужно автоматизировать,осознанно подходим к команде, не зовём друзей «просто так в долю», разделяем роли: продукт, продажи, маркетинг, операции, финансы, для каждого партнёра формулируем, за что он отвечает, какими ресурсами и навыками приносит ценность, как делится доля и как принимаются решения, проговариваем неприятные вещи заранее: что будет, если кто-то уйдёт, выгорит, передумает, фиксируем договорённости хотя бы в простом документе,отделяем долгосрочное видение от коротких итераций, держим в голове, куда хотим прийти через 3–5 лет, но планируем только ближайшие 1–3 месяца конкретными экспериментами и шагами, после каждого цикла задаём себе вопросы: что мы планировали, что реально сделали, что сработало, что нет, что меняем в гипотезах и процессах,думаем о рисках и «стоп-условиях», заранее определяем, сколько времени и ресурсов готовы вложить в эту идею, какие критерии успеха/провала, при каких условиях честно признаём, что пора менять идею или формат, не привязываемся к конкретному решению сильнее, чем к проблеме клиентов, при необходимости делаем pivot, меняем сегмент, канал, модель, но учимся на собранных данных, а не начинаем каждый раз совсем с нуля,не забываем про себя и людей, не строим бизнес как марафон без сна и здоровья, закладываем устойчивый ритм, умение говорить «нет» лишним фичам и задачам, формируем культуру: честность, уважение к клиентам, прозрачность в команде, готовность признавать ошибки и улучшать систему,и на всех этапах построения бизнеса из идеи поддерживаем один и тот же принцип: сначала проблема и человек, потом гипотеза и эксперимент, потом измерения и выводы, только потом масштабирование, чёткие границы ответственности, явные договорённости, простая математика, небольшие, но постоянные шаги каждый день.
---
start not from registering a company, designing a logo and finding the “perfect” name, but from understanding the *problem* you want to solve, describe your idea not as a feature (“build an app”) but as a *pain and value* (“people struggle with X, I’ll help them do Y faster/cheaper/easier”), define one specific target person instead of “everyone”, check if these people have real motivation and are willing to change their behavior for your value,take the idea out of your head into reality, don’t write a 50-page business plan first, instead turn it into a set of testable hypotheses, define: who the customer is, what pain they have, what benefit they get, how they discover you, what they pay for and how, what resources and partners you need, what margin you want, for each hypothesis design a simple test — conversation, survey, prototype, landing page, pilot,start with talking, not coding, find 10–20 potential customers and run honest interviews about their current processes and problems, ask not “do you like my idea” but “how do you solve this today, how much time/money does it cost, what annoys you the most”, write everything down, don’t argue and don’t pitch, look for repeating patterns, if people don’t see a problem, admit that the idea is weak and either change the segment or the idea,craft a simple value proposition in plain language, one clear sentence: “for [who], who struggle with [problem], we deliver [result] in [time/price/effort] better than [alternative]”, avoid buzzwords like “innovative AI platform”, be concrete: “cut X from 1 hour to 5 minutes”, “reduce costs by Y %”,build a *minimum viable product* (MVP), not a “perfect v1.0”, cut everything that doesn’t directly create value, keep only 1–2 key pieces that actually solve the pain, the MVP doesn’t have to be an app — it can be a form, a chat, a spreadsheet, a manual service, a bot, anything that lets you test “do people actually use this and pay for it”,choose a business model and do simple unit economics on a napkin, estimate how much it costs to acquire one customer, how much they pay, what your variable cost is, what margin is left, if the model doesn’t work even on paper, admit it and change pricing, format or segment, don’t hide behind “we’ll scale later and it will magically work”,start with simple acquisition channels, don’t burn budget on brand ads, begin with things you can do manually: personal contacts, niche chats and communities, cold emails, partnerships, niche social media, treat each channel as an experiment, measure: how many people saw, responded, tried, paid,in parallel, design the business as a system, define key metrics: number of leads, conversions at each step, retention, average order value, LTV, unit economics, don’t drown in hundreds of metrics, pick 3–5 critical ones and make decisions based on them, build simple reports even if it’s just a spreadsheet,think about legal and finances, but don’t use them as an excuse “I can’t do anything until I register a company”, first validate the idea with tiny test transactions, once there’s clear demand, choose a legal form depending on your country, taxes and risk, if you’re under 18, talk formal steps through with your parents/guardians and follow local laws, from the very start try to track income and expenses, even if it’s a basic table,design product and processes so they can *scale*, don’t tie everything to a single person, don’t build a system where all key knowledge lives only in your head, document simple processes (how you sell, how you deliver, how you count money), add minimal quality standards, think what could be automated later, but at first do it manually to learn what’s really worth automating,be deliberate about the team, don’t invite friends “just to give them equity”, separate roles: product, sales, marketing, operations, finance, for each partner define what they own, what skills and resources they bring, how equity is split and how decisions are made, discuss the hard stuff early: what happens if someone leaves, burns out, or changes their mind, write these agreements down in a simple document,separate long-term vision from short iterations, keep in mind where you want to be in 3–5 years, but plan the next 1–3 months as concrete experiments and steps, after each cycle ask yourself: what did we plan, what did we actually do, what worked, what didn’t, what do we change in our hypotheses and processes,think about risks and “stop conditions”, decide in advance how much time and money you’re willing to invest in this idea, what counts as success/failure, at which point you’ll honestly say “we need to change direction”, don’t be more attached to a specific solution than to the customer’s problem, when needed, pivot — change segment, channel, model — but reuse the data you’ve gathered instead of starting from scratch every time,don’t forget about yourself and your people, don’t build a business as a burnout marathon with no sleep and no life, set a sustainable pace, learn to say “no” to extra features and distractions, create a culture of honesty, respect for customers, transparency in the team, willingness to admit mistakes and improve the system,and at every stage of turning an idea into a business stick to the same loop: first the problem and the person, then hypothesis and experiment, then measurement and conclusions, only then scaling, clear responsibilities, explicit agreements, simple math, small but consistent steps every day.
### CRM
начинаем не с выбора CRM-системы и набора полей в карточке, а с понимания **бизнес-целей и воронок**, формулируем, зачем нам CRM: увеличить конверсию, сократить цикл сделки, снизить отток, улучшить сервис, выстраиваем карту **путешествия клиента** (от первого касания до повторных продаж и поддержки), описываем этапы: лид → квалификация → сделка → онбординг → сопровождение → апселл/реактивация, для каждого этапа фиксируем, что делает клиент, что делает команда, какие данные нужны, какие события должны фиксироваться,строим **доменную модель CRM**, а не «рандомные сущности»: чётко определяем, что у нас такое аккаунт (компания), контакт (человек), лид, возможность/сделка, активность (звонок, письмо, встреча), продукт/тариф, подписка, тикет поддержки, договор, определяем связи (контакты принадлежат аккаунтам, сделки привязаны к аккаунту и ответственному, активности к контактам/сделкам), задаём минимальный, но осмысленный набор полей с понятными определениями, избегаем «произвольных кастом-филдов без смысла»,отталкиваемся от **процессов**, а не от структуры отдела, моделируем end-to-end-процессы: лид попал из источника → зафиксирован в CRM → назначен ответственному → квалифицирован → переведён в сделку или отбракован с причиной, описываем правила: кто когда что должен сделать, какие статусы допустимы, какие переходы запрещены, не плодим 50 статусов сделки «на все случаи жизни», держим pipeline коротким и понятным,определяем **единую точку правды о клиенте**: какие данные живут в CRM как системе учёта отношений, какие — в биллинге, продукте, сервис-деске, чётко разграничиваем мастер-системы (где первичная запись), не пытаемся запихнуть в CRM всё подряд (логи продукта, огромные документы, аналитические витрины), интегрируемся через API и события, а не через ручные выгрузки,проектируем CRM как **модульную архитектуру**: ядро (аккаунты, контакты, сделки, активности), модули продаж, маркетинга, поддержки, партнёрских каналов, для каждого модуля определяем чёткие границы и точки интеграции, настраиваем расширения через конфигурацию и кастомные объекты, но следим, чтобы кастомизации не ломали обновления и поддерживаемость,делаем CRM **API-first**: любые ключевые операции (создание/обновление контактов, сделок, задач, тикетов) доступны через стабильный API, интегрируем веб-формы, сайт, продукт, телефонию, почту, мессенджеры не «скриптами в обход», а через согласованные интерфейсы, используем **событийную модель** там, где важно реагировать в реальном времени (событие «сделка закрыта», «подписка продлена», «тикет просрочен»),проектируем **данные и качество**: заводим уникальные идентификаторы для аккаунтов и контактов, определяем правила дедупликации, источники обогащения данных (email, телефон, домен компании), настраиваем валидации и обязательные поля там, где без них аналитика ломается, продумываем стратегию миграций при смене CRM или реструктуризации полей, заранее закладываем экспорт/архивирование,делаем интерфейс **роль-ориентированным**: для продавца — минимальный список полей и задач по его сделкам, для руководителя — дашборды по воронке и активности команды, для саппорта — быстрый доступ к истории обращений и договорным условиям, не загружаем всех одним и тем же перегруженным экраном, скрываем лишнее по ролям и профилям доступа,строим **автоматизацию осмысленно**: задаём триггеры (новый лид, статус сделки поменялся, срок SLA подходит), автоматом создаём задачи/напоминания, отправляем письма/уведомления только там, где это реально экономит время и снижает риск забывания, избегаем «воркфлоу-спагетти», где никто уже не понимает, почему что-то произошло, тщательно документируем все правила автоматизации,продумываем **аналитику и метрики** заранее: какие отчёты критичны (конверсия поэтапно, воронка по каналам, цикл сделки, причина проигрыша, NPS, отток, загрузка менеджеров), конструируем структуру данных так, чтобы эти отчёты считались без «Excel-маги», интегрируем CRM с DWH/BI, но не превращаем саму CRM в единственный аналитический инструмент,жёстко соблюдаем **безопасность и приватность**: роли, права, разграничение доступа к клиентам по командам/регионам, аудит изменений (кто, когда, что поменял), соблюдаем требования GDPR/аналоги (согласие на рассылки, право на забвение, минимизация PII), не храним в CRM лишние чувствительные данные, которые не нужны для бизнес-процесса,организуем **внедрение поэтапно**: сначала один сегмент или одна команда, один ключевой процесс (например, продажи), доводим до работающего состояния, обучаем, собираем обратную связь, только потом расширяем на поддержку, маркетинг, партнёров, даём пользователям «победы» — быстрые улучшения, а не только «ещё один инструмент для отчётности»,делаем CRM **расширяемой и сменяемой**: минимизируем vendor lock-in, всё, что критично — дублируем через API/выгрузки в независимое хранилище, не завязываем уникальную бизнес-логику на проприетарные фичи, которые нельзя повторить вне этой системы, документируем настройки, интеграции, схемы данных, чтобы при росте компании можно было донастроить или сменить решение без катастрофы,и на всех этапах построения CRM-системы и процессов держим один принцип: сначала **процессы и люди**, потом **данные и модели**, потом **инструменты и автоматизация**, CRM — не «волшебная коробка», а отражение вашей реальной работы с клиентом, и чем чётче вы задаёте доменную модель, права, интеграции и метрики, тем больше CRM помогает, а не мешает.
---
start not from picking a CRM tool and stuffing fields into a contact form, but from **business goals and funnels**, define why you need a CRM at all: increase conversion, shorten sales cycle, reduce churn, improve service, map the **customer journey** (from first touch to repeat sales and support), write down the stages: lead → qualification → opportunity/deal → onboarding → ongoing service → upsell/reactivation, for each stage define what the customer does, what your team does, what data you need, which events must be recorded,build a **CRM domain model**, not random entities: clearly define what an account (company) is, a contact (person), a lead, an opportunity/deal, an activity (call, email, meeting), a product/plan, a subscription, a support ticket, a contract, define relationships (contacts belong to accounts, deals linked to account and owner, activities tied to contacts/deals), set a minimal but meaningful set of fields with clear definitions, avoid “arbitrary custom fields with no shared meaning”,drive design from **processes**, not org chart, model end-to-end flows: lead arrives from source → captured in CRM → assigned to owner → qualified → converted to deal or disqualified with a reason, define rules: who does what and when, which statuses are allowed, which transitions are forbidden, don’t create 50 deal stages “for every nuance”, keep pipelines short and understandable,define a **single source of truth about the customer**: which data lives in CRM as the relationship system of record, which in billing, product, service desk, clearly mark master systems (where a record originates), don’t try to stuff everything into CRM (raw product logs, huge documents, analytical cubes), integrate via APIs and events instead of manual exports,design CRM as a **modular architecture**: core (accounts, contacts, deals, activities), plus modules for sales, marketing, support, partner channels, for each module define boundaries and integration points, use configuration and custom objects for extensions, but ensure customizations don’t break upgrades and maintainability,make the CRM **API-first**: any key operation (create/update contacts, deals, tasks, tickets) must be available via stable APIs, integrate web forms, website, product, telephony, email, messengers not with one-off hacks but through agreed interfaces, use an **event model** wherever near-real-time reactions matter (events like “deal closed”, “subscription renewed”, “ticket breached SLA”),design **data and quality**: unique IDs for accounts and contacts, deduplication rules, data enrichment sources (email, phone, company domain), validations and required fields where missing data would break analytics, plan for migrations when you restructure the CRM or switch vendors, bake in export/archiving strategies from day one,make UX **role-based**: for a sales rep — minimal fields and tasks around their own deals, for a manager — funnels and team activity dashboards, for support — fast access to interaction history and contractual terms, don’t show the same overloaded screen to everyone, hide irrelevant objects/fields per role and permission,build **automation intentionally**: define triggers (new lead, deal stage change, SLA approaching), create tasks/reminders automatically, send emails/notifications only where they truly save time and reduce forgetfulness, avoid “workflow spaghetti” where nobody understands why something happened, document all automation rules carefully,think through **analytics and metrics** upfront: which reports are critical (conversion by stage, funnel by channel, cycle time, loss reasons, NPS, churn, rep workload), shape the data model so these reports can be computed without “Excel wizardry”, integrate CRM with DWH/BI, but don’t turn the CRM itself into the only analytics tool,enforce **security and privacy** strictly: roles, permissions, territory/segment-based access, change audit (who changed what when), comply with GDPR/local equivalents (consent for marketing, right to be forgotten, data minimization), don’t store unnecessary sensitive data in CRM just because you can,roll out **incrementally**: start with one segment or one team, one key process (e.g. new business sales), get it to work, train people, gather feedback, only then expand to support, marketing, partners, deliver early “wins” — tangible improvements — not just “another reporting tool”,keep the CRM **extensible and replaceable**: minimize vendor lock-in, mirror critical data through APIs/exports into an independent store, don’t anchor unique business logic to proprietary features that are impossible to reproduce elsewhere, document configurations, integrations and schemas so that as you grow you can reconfigure or even swap solutions without disaster,and at every stage of building your CRM and processes keep the same principle: first **processes and people**, then **data and models**, then **tools and automation**, a CRM is not a magic box but a mirror of how you actually work with customers, and the clearer you define domain model, permissions, integrations and metrics, the more your CRM helps instead of getting in the way.
#### marketing
начинаем не с «креативных идей ради креатива», а с понимания продукта и человека, которому мы его продаём, формулируем, какую реальную боль, мечту или желание закрывает продукт, чем он отличается от всего вокруг, собираем язык целевой аудитории — как они сами описывают свою проблему, свои страхи и хотелки, строим портреты не абстрактных «женщин 25–45», а конкретных героев с образом жизни, привычками и контекстом, превращаем продукт не в набор фич, а в **героя истории**, где пользователь — главный персонаж, а наш продукт — инструмент, который помогает ему победить, придумывая идеи, всегда начинаем с инсайта: «почему людям вообще должно быть не всё равно», тестируем инсайты на живых людях, а не только в голове и чатах, говорим языком преимуществ, а не только характеристик («спишь дольше», «сдаёшь проект без нервов», а не «8 ГБ RAM»), превращаем сухие факты в яркие образы, метафоры и понятные сравнения, не влюбляемся в первую идею — генерим десятки вариантов, меняем углы, форматы, медиумы, не боимся странных мыслей, потом отсекаем всё, что не связано с реальной ценностью продукта, строим креатив вокруг **одного ядра** (одна мысль, один конфликт, один сильный образ), не пытаемся сказать всё сразу в одном баннере, выбираем формат под задачу: сториз — эмоция и импульс, лонгрид/лендинг — объяснение и аргументы, видео — история и атмосфера, статичный креатив — чистый ударный посыл, держим бренд как «скелет» и правила, а креатив как живую кожу — узнаваемый тон, визуальный мир, принципы, но свобода внутри, следим, чтобы даже самая дикая идея не разрушала доверие к бренду, не обещаем того, чего продукт не умеет, не играем на уязвимостях людей токсично, думаем не только «как привлечь», но и «что человек почувствует после покупки», строим коммуникацию на уважении, честности и ясности, держим в голове воронку: креатив — это не только awareness, но и интерес, и желание, и действие, видим, где наш креатив включается в путь пользователя, закладываем **измеримость**: перед запуском решаем, что считаем (клики, удержание, вовлечение, покупки, повторные заказы), делаем не один идеальный ролик, а пачку вариантов, A/B тестируем заголовки, визуалы, офферы, учимся на цифрах, а не только на «чувстве прекрасного», осознанно работаем с ограничениями: бюджет, формат, правила площадок — не повод унывать, а рамка для находчивости, ищем простые и дешёвые идеи, которые можно снять и собрать даже на минимальных ресурсах, думаем кросс-канально — как одна идея может жить в разных форматах (мем, баннер, видео, офлайн-активация), сохраняем **континуитет**: чтобы человек, увидев нас в разном месте, узнавал тот же характер и голос, строим креатив не в одиночку — собираем фидбек от команды, продукта, продаж, людей из целевой аудитории, защищая идею, объясняем не «это красиво», а «это решает вот эту задачу и попадает в вот такой инсайт», относимся к конкуренции как к источнику вдохновения и контраста — изучаем их креатив, чтобы понять, как *отстроиться*, а не скопировать, постоянно собираем библиотеку примеров — удачных и провальных, разбираем, почему они сработали или нет, помним, что креатив — это не только визуал и слоган, но и **упаковка продукта, оффер, бонусы, формат доставки, микро-сообщения в интерфейсе**, и везде держим одну линию: чёткая ценность, живой человеческий язык, уважение к пользователю, честность и смелость, которая не стыдно показать через год.
start not from “cool ideas for the sake of coolness”, but from understanding the product and the person you’re talking to, define which real pain, dream or desire the product addresses and how it’s different from everything around it, collect the audience’s own language — how they describe their problem, fears and wishes, build portraits of *specific* people, not abstract “18–35 yo”, turn the product from a pile of features into the **hero’s tool** in a story where the user is the main character and your product helps them win, when ideating, always start from an insight: “why should anyone care at all?”, test insights on real people, not only in your head or with colleagues, speak in benefits not just features (“sleep longer”, “submit on time without panic” instead of “8 GB RAM”), turn dry facts into vivid images, metaphors and concrete comparisons, don’t fall in love with the first idea — generate tens of options, change angles, formats and mediums, allow weird thoughts, then ruthlessly cut everything that doesn’t support real product value, build each creative around **one core** (one thought, one conflict, one strong image), don’t try to say everything in one ad, pick the format for the job: stories — emotion and impulse, longform/landing — explanation and proof, video — story and atmosphere, static — one sharp message, treat the brand as the “skeleton” and rules, and creativity as the living skin — recognizable tone, visual world and principles, but freedom inside, ensure even the wildest idea doesn’t destroy trust: don’t promise what the product can’t deliver, don’t exploit people’s vulnerabilities in a toxic way, think not only “how to get the click”, but “what they will feel after they buy”, build communication on respect, honesty and clarity, keep the funnel in mind: creative is not just for awareness, but for interest, desire and action too, know where your creative fits into the user journey, make it **measurable**: before launch decide what you’re optimizing for (clicks, watch time, engagement, purchases, repeat orders), don’t make a single “perfect” asset, create variations, A/B test headlines, visuals and offers, learn from data, not just your aesthetic taste, treat constraints (budget, formats, platform rules) as a frame for ingenuity, not as excuses, look for simple, cheap ideas you can shoot and assemble with minimal resources, think cross-channel — how one idea can live as memes, banners, videos, offline activations, keep **continuity** so that people recognize the same character and voice across channels, don’t create in a vacuum — gather feedback from product, sales and real users, when defending an idea, explain not “it looks cool” but “it solves this problem and hits this insight”, treat competitors as a source of inspiration and contrast — study their creative to understand how to *differentiate*, not copy, constantly build your swipe file of good and bad ads, dissect why they worked or failed, remember that “creative” isn’t just visuals and slogan but also **product packaging, offer, bonuses, delivery format, microcopy in the interface**, and everywhere keep one line: clear value, human language, respect for the user, honesty and boldness you won’t be ashamed of a year later.
### Обобщенно
start with a clear domain model, separate business logic from infrastructure concerns, design around use-cases not technical layers, ensure each module has a single focused responsibility, define stable interfaces for all external dependencies, inject dependencies instead of hard-coding them, avoid tight coupling between modules, program to abstractions not concrete implementations, define contracts via interfaces or protocols, design extension points without modifying stable core logic, encapsulate complexity behind simple boundaries, prefer composition over inheritance, structure the system into domain, application, infrastructure, and presentation layers, externalize configuration away from code, avoid mixing business rules with IO operations, keep logic pure when feasible, separate read and write paths when beneficial, use lightweight data structures for pure data transport, implement adapters for external systems, ensure every component has exactly one reason to change, build minimal and stable public APIs, eliminate god-objects and oversized manager classes, prevent leaking of internal details across boundaries, keep all dependencies explicit and avoid global state, design everything for testability from the start, unit-test domain behavior thoroughly, log important events at system edges, treat errors as a part of the public interface, ensure components can be replaced or evolved independently, achieve performance through solid architecture not hacks, document architectural decisions concisely with ADRs, maintain strict separation of concerns and clear boundaries between all layers
начинаем с формулирования доменной модели, отделяем бизнес-логику от инфраструктурных деталей, проектируем систему вокруг пользовательских сценариев а не технических слоёв, даём каждому модулю одну чёткую ответственность, определяем стабильные интерфейсы для всех внешних зависимостей, внедряем зависимости вместо жёсткого связывания, избегаем плотной связности модулей, программируем против абстракций а не конкретных реализаций, задаём контракты через интерфейсы или протоколы, проектируем точки расширения без изменения устойчивого ядра, инкапсулируем сложность за простыми и понятными границами, предпочитаем композицию наследованию, разделяем систему на слои: домен, приложение, инфраструктура, представление, выносим конфигурацию за пределы кода, избегаем смешивания бизнес-правил и операций ввода-вывода, сохраняем логику максимально чистой там где возможно, разделяем операции чтения и записи когда это целесообразно, используем лёгкие структуры данных для переноса информации, создаём адаптеры для внешних систем, гарантируем что каждый компонент имеет одну единственную причину для изменения, проектируем публичные API минимальными и стабильными, избегаем god-objects и гигантских менеджеров, предотвращаем утечку деталей реализации за пределы модулей, делаем зависимости явными и исключаем глобальные состояния, изначально проектируем код тестируемым, покрываем доменное поведение модульными тестами, логируем важные события на границах системы, рассматриваем ошибки как часть публичного контракта, обеспечиваем возможность замены или эволюции любого компонента независимо от остальных, достигаем производительности архитектурой а не хаотичными оптимизациями, документируем архитектурные решения через краткие ADR, поддерживаем строгие границы и чистое разделение ответственности между всеми слоями
====== gpt 5.1 extended thinking
начинаем с понимания домена и формулирования бизнес-целей, собираем и фиксируем функциональные и нефункциональные требования, формируем общее словарное ядро и единый язык терминов с бизнесом, явно описываем границы системы и контексты взаимодействия, проектируем архитектуру от домена и сценариев использования а не от фреймворков, разделяем бизнес-логику и инфраструктуру на уровне идей и на уровне кода, строим систему вокруг use-case / application слоёв, избегаем прямой зависимости домена от баз данных, сетей и UI, сначала определяем модель домена, сущности и их инварианты, выделяем сущности, value-объекты и их ответственность, определяем агрегаты и их границы, проектируем доменные события и реакции на них, определяем интерфейсы репозиториев и сервисов домена, применяем паттерн порты и адаптеры (hexagonal) для изоляции инфраструктуры, каждая часть системы имеет одну чёткую ответственность, строго следуем SRP — один модуль, одна причина для изменения, следуем OCP — расширяем поведение через новые реализации вместо изменения стабильного кода, следуем LSP — подтипы не ломают ожидания клиентов, следуем ISP — дробим громоздкие интерфейсы на небольшие целевые, следуем DIP — высокоуровневый код зависит от абстракций а не от деталей, явно объявляем интерфейсы для всех внешних зависимостей, внедряем зависимости через конструкторы, фабрики или DI-контейнер, избегаем сервис-локаторов и скрытых глобальных синглтонов, проектируем модули с минимальными и понятными API, делаем связи между модулями направленными и как можно более слабыми, предпочитаем композицию наследованию при расширении поведения, наследование используем только для истинных иерархий и полиморфизма, избегаем god-objects и «менеджеров всего на свете», не позволяем утекать деталям реализации через публичные интерфейсы, держим модули маленькими и фокусными, декомпозируем сложные классы на более простые, явно разделяем слои: домен, приложение, инфраструктура, представление, не допускаем зависимостей из внутренних слоёв к внешним, UI зависит от application-слоя а не от домена напрямую, инфраструктура реализует порты домена а не диктует ему форму, выносим всю конфигурацию (URL, ключи, флаги) за пределы кода, применяем конфигурацию через окружение, конфиг-файлы и секрет-хранилища, не хардкодим чувствительные данные в репозитории, не смешиваем бизнес-логику с IO-операциями, работа с сетью, файлами и БД сконцентрирована в адаптерах и инфраструктуре, держим как можно больше функций чистыми (без побочных эффектов), разделяем операции чтения и записи там где это даёт пользу (CQRS-подход), для чтения используем простые оптимизированные модели, для записи — строгие доменные модели, используем простые структуры данных / DTO для переноса данных между слоями, не тащим доменные сущности в инфраструктурные и транспортные слои, проектируем контракты API стабильными и обратно совместимыми, версионируем внешние API и события, чётко описываем схемы сообщений и форматы данных, явно обрабатываем ошибки и исключения, проектируем ошибки и коды ответов как часть контракта, различаем бизнес-ошибки и технические сбои, не глушим исключения без логирования и реакции, валидацию данных проводим как на границах системы так и внутри домена, инварианты домена защищаются самим доменом а не только UI, проектируем безопасность с самого начала а не «потом», учитываем аутентификацию, авторизацию, аудит и приватность данных, минимизируем поверхность атаки и не раскрываем лишние детали, логируем ключевые события на границах системы и в критичных точках домена, используем структурированные логи для дальнейшего анализа, добавляем метрики и трассировку для наблюдаемости, проектируем систему так чтобы её можно было мониторить и алертить, выбираем протоколы и форматы взаимодействия исходя из требований а не моды, проектируем систему с учётом ошибок сети, задержек и временной недоступности сервисов, применяем ретраи, таймауты, circuit breaker там где это нужно, думаем о идемпотентности операций при повторных запросах, обеспечиваем корректную миграцию данных и эволюцию схем, закладываем стратегию миграций БД и обратной совместимости, используем feature-флаги для безопасного включения новых возможностей, проектируем код так чтобы можно было катить по частям и откатывать изменения, думаем о масштабировании по оси чтения и по оси записи, сначала проектируем простую масштабируемую архитектуру монолита, разделяем систему на сервисы только при реальной необходимости, при микросервисах чётко определяем границы контекстов и владение данными, минимизируем синхронные межсервисные зависимости, используем асинхронное взаимодействие и события когда это оправдано, избегаем распределённых транзакций и компенсируемых операций без необходимости, проектируем соглашения об idempotency и согласованности данных, заранее думаем о миграции схем сообщений и контрактов, пишем код так чтобы его было легко тестировать, отделяем бизнес-логику от фреймворков, покрываем домен юнит-тестами, проверяем интеграцию на уровне адаптеров и границ, используем тестовые двойники для внешних зависимостей, автоматизируем ключевые сценарии end-to-end тестами, поддерживаем быстрый и надёжный тестовый прогон, интегрируем тесты в CI/CD конвейер, проектируем структуру каталогов и модулей отражая архитектуру а не случайно, называем пакеты и пространства имён по ролям и слоям, используем читаемые и говорящие имена для классов, методов и переменных, избегаем абстрактных названий вроде Manager, Helper, Utils без контекста, документируем публичные контракты и ключевую бизнес-логику, избегаем избыточных комментариев к очевидному коду, вместо комментариев улучшаем структуру и названия, применяем код-ревью как обязательный этап, обсуждаем архитектурные решения и trade-off на уровне команды, фиксируем архитектурные решения в коротких ADR, регулярно пересматриваем архитектуру с учётом новых требований но не переписываем всё без необходимости, отделяем временные решения и долгосрочные, помечаем технический долг и планируем его погашение, следуем единым код-стайлам и форматированию, автоматизируем линтеры и форматеры, не оптимизируем преждевременно, сначала делаем код понятным и корректным, оптимизацию проводим только после измерений, используем профилирование для поиска узких мест, отказываемся от «магии» и слишком умных решений в пользу простоты и явности, сознательно ограничиваем стек технологий и фреймворков, минимизируем количество точек отказа, проектируем систему так чтобы отдельные компоненты можно было разворачивать, обновлять и заменять независимо, обеспечиваем обратную совместимость при эволюции протоколов и моделей, делаем систему устойчивой к частичным отказам, проектируем graceful degradation и fallback-сценарии, думаем о эксплуатационной стороне: логирование, мониторинг, алерты, дашборды, автоматизируем сборку, тесты, деплой и миграции, проектируем архитектуру так чтобы новому разработчику было легко войти, поддерживаем документацию в актуальном состоянии, упрощаем архитектуру когда можно не усложнять, постоянно балансируем между идеальной чистотой и прагматичностью, и везде где возможно поддерживаем строгие границы, явные контракты и чёткое разделение ответственности между слоями, модулями и командами
start with understanding the domain and defining business goals, gather and document functional and non-functional requirements, build a shared glossary and ubiquitous language with the business, explicitly describe system boundaries and interaction contexts, design the architecture from the domain and use cases rather than from frameworks, separate business logic and infrastructure at the conceptual level and in code, build the system around use-case / application layers, avoid direct dependencies from the domain to databases, networks, and UI, first define the domain model, entities, and their invariants, distinguish entities, value objects, and their responsibilities, define aggregates and their boundaries, design domain events and their reactions, define interfaces of repositories and domain services, apply the ports-and-adapters (hexagonal) pattern to isolate infrastructure, ensure each part of the system has one clear responsibility, strictly follow SRP — one module, one reason to change, follow OCP — extend behavior via new implementations instead of modifying stable code, follow LSP — subtypes do not break client expectations, follow ISP — split fat interfaces into small, focused ones, follow DIP — high-level code depends on abstractions, not details, explicitly declare interfaces for all external dependencies, inject dependencies via constructors, factories, or a DI container, avoid service locators and hidden global singletons, design modules with minimal and understandable APIs, keep dependencies between modules one-directional and as weak as possible, prefer composition over inheritance when extending behavior, use inheritance only for true hierarchies and polymorphism, avoid god objects and “manager of everything” classes, do not let implementation details leak through public interfaces, keep modules small and focused, decompose complex classes into simpler ones, explicitly separate layers: domain, application, infrastructure, presentation, do not allow dependencies from inner layers to outer layers, let the UI depend on the application layer rather than the domain directly, let infrastructure implement domain ports instead of dictating domain shape, move all configuration (URLs, keys, flags) out of code, apply configuration via environment, config files, and secret stores, never hardcode sensitive data in the repository, do not mix business logic with IO operations, keep network, file, and database access concentrated in adapters and infrastructure, keep as many functions as possible pure (without side effects), separate read and write operations where it brings value (CQRS-style), use simple optimized models for reads, use strict domain models for writes, use simple data structures / DTOs to transfer data between layers, do not drag domain entities into infrastructure and transport layers, design API contracts to be stable and backward compatible, version external APIs and events, describe message schemas and data formats clearly, handle errors and exceptions explicitly, design errors and response codes as part of the contract, distinguish business errors from technical failures, never swallow exceptions without logging and appropriate handling, validate data both at system boundaries and inside the domain, let domain invariants be enforced by the domain itself, not only by the UI, design security from the beginning, not “later”, account for authentication, authorization, audit, and data privacy, minimize the attack surface and avoid exposing unnecessary details, log key events at system boundaries and in critical domain points, use structured logging for further analysis, add metrics and tracing for observability, design the system so it can be monitored and alerted, choose communication protocols and formats based on requirements, not fashion, design the system with network errors, latency, and temporary service unavailability in mind, apply retries, timeouts, and circuit breakers where appropriate, think about idempotency of operations under retries, ensure correct data migration and schema evolution, plan a database migration and backward-compatibility strategy, use feature flags to enable new capabilities safely, design code so it can be rolled out incrementally and rolled back safely, think about scaling along the read axis and the write axis, start with a simple, scalable monolith architecture, split the system into services only when there is real need, when using microservices, clearly define bounded contexts and data ownership, minimize synchronous inter-service dependencies, use asynchronous communication and events when justified, avoid distributed transactions and compensating operations unless necessary, design conventions for idempotency and data consistency, think ahead about schema evolution for messages and contracts, write code so that it is easy to test, separate business logic from frameworks, cover the domain with unit tests, test integration at the level of adapters and boundaries, use test doubles for external dependencies, automate key end-to-end scenarios, keep the test suite fast and reliable, integrate tests into the CI/CD pipeline, design folder and module structure to reflect the architecture, not accidents, name packages and namespaces according to roles and layers, use readable, meaningful names for classes, methods, and variables, avoid abstract names like Manager, Helper, Utils without context, document public contracts and key business logic, avoid excessive comments on obvious code, improve structure and naming instead of relying on comments, treat code review as a mandatory step, discuss architectural decisions and trade-offs at the team level, record architectural decisions in short ADRs, regularly revisit the architecture in light of new requirements but avoid full rewrites without strong reasons, distinguish temporary solutions from long-term ones, mark technical debt and plan how to pay it off, follow a consistent code style and formatting, automate linters and formatters, do not optimize prematurely, first make the code clear and correct, optimize only after measurement, use profiling to find bottlenecks, avoid “magic” and overly clever solutions in favor of simplicity and explicitness, deliberately limit the technology and framework stack, minimize the number of single points of failure, design the system so that individual components can be deployed, updated, and replaced independently, ensure backward compatibility when evolving protocols and models, make the system resilient to partial failures, design graceful degradation and fallback scenarios, think about operability: logging, monitoring, alerts, dashboards, automate build, tests, deployment, and migrations, design the architecture so that new developers can onboard easily, keep documentation up to date, simplify the architecture whenever you can instead of complicating it, continuously balance between ideal cleanliness and pragmatism, and wherever possible maintain strict boundaries, explicit contracts, and clear separation of responsibilities between layers, modules, and teams
# microservices
start from business capabilities and bounded contexts, define services around domain boundaries not technical layers, keep each microservice small, cohesive, and with a single clear responsibility, give each service its own data store and schema, avoid sharing databases across services, communicate between services via well-defined APIs not shared tables, favor explicit contracts over implicit coupling, design services to be independently deployable and independently scalable, optimize for autonomy of teams owning services, treat each service as a separately versioned product, keep the domain logic inside the service and infrastructure at the edges, avoid chatty fine-grained RPC between services, prefer coarse-grained interactions, use synchronous calls only where necessary and latency-tolerant, favor asynchronous messaging and events for integration and decoupling, use an API gateway or edge layer to shield clients from internal topology, avoid letting external consumers call internal services directly, design idempotent operations for safe retries, model eventual consistency explicitly in workflows and UX, avoid distributed transactions where possible, use sagas and compensating actions for cross-service workflows, make failure a first-class case when designing flows, apply timeouts, retries with backoff, and circuit breakers on service calls, limit the fan-out of synchronous calls per request, design bulkheads to isolate failures between components, ensure each service can start, stop, and restart without human intervention, keep service startup order loosely coupled, externalize configuration and secrets for each service, use environment-based configuration and centralized secret management, standardize logging, metrics, and tracing across all services, include correlation IDs in logs and traces for request flows, make every service observable: logs, metrics, health checks, readiness and liveness probes, define clear SLOs and error budgets per service, expose lightweight health endpoints for orchestration and load balancers, automate deployment, rollback, and scaling for each service, use containers as a default packaging mechanism, rely on orchestration (like Kubernetes-style) for scheduling and resilience, build immutable artifacts and promote them across environments, keep CI/CD pipelines per service and keep them fast, test services in isolation with contract tests, use consumer-driven contracts to protect integrations, minimize reliance on large, brittle end-to-end tests, keep APIs backward compatible and version them when breaking changes are needed, treat API schemas and message formats as code under version control, deprecate old API versions with clear timelines, document APIs clearly and keep docs close to code, prefer stable interfaces over clever ones, avoid leaking internal implementation details through public APIs, avoid central “god services” that everything depends on, design for decentralized governance with a small set of global standards, limit the number of technologies and runtimes to reduce cognitive load, but allow justified exceptions, keep shared libraries minimal and focused to avoid tight coupling, prefer sharing concepts via documentation and APIs not shared code, think about multi-tenancy and data isolation from day one, encrypt data in transit and at rest, design authorization and authentication at the service boundary, use a consistent identity and access model across services, avoid duplicating security logic in ad hoc ways, consider a zero-trust approach between services, validate inputs at the edge of each service, enforce domain invariants inside the service’s own domain logic, keep the service’s internal model separate from external DTOs and transport models, don’t let UI or other services dictate internal domain structure, avoid premature splitting into too many microservices, start from a well-structured modular monolith and extract services when boundaries are clear, identify true hotspots for independent scaling or independent change, extract those into services first, watch for distributed monolith anti-patterns where everything still changes together, measure communication patterns and refactor boundaries when necessary, design rollout strategies that respect backward compatibility, use feature flags for behavior changes within services, coordinate long-lived migrations carefully across services and consumers, consider data migration strategies and dual-write or dual-read phases, expose domain events as a primary integration mechanism where appropriate, design event schemas carefully to avoid tight consumer coupling, think through event ordering, idempotency, and replay, avoid overusing event-driven design where a simple synchronous API is enough, keep each service’s codebase small and clean with a clear folder layout, enforce SOLID and clean code principles inside each microservice, write tests close to the behavior, not just lines of code, maintain service-level runbooks and operational docs, standardize dashboards and alerts for critical signals, make it easy to see the health of the whole system at a glance, practice chaos and failure injection in non-production to validate resilience, design security, observability, and operations as cross-cutting pillars, ensure onboarding a new engineer to one service is fast and low-friction, regularly revisit the service landscape and merge or split services when needed, prefer evolving the architecture incrementally over big-bang redesigns, constantly balance service granularity, team autonomy, and operational overhead, and always keep microservices as a tool to serve the domain and the teams, not as a goal in itself
##### FOR MCP SERVERS
начинаем с определения бизнес-способностей и данных, которые вообще имеет смысл открывать через MCP, проектируем каждый MCP-сервер вокруг одного чёткого bounded context и одной основной внешней системы, а не вокруг набора случайных тулов, формулируем доменную модель того, что сервер даёт модели (ресурсы, инструменты, промпты), а уже потом выбираем SDK и транспорт, отделяем доменную логику сервера от протокола MCP и конкретных API внешней системы, инкапсулируем детали внешнего API в адаптерах, не даём им протечь в описания tools/resources, проектируем схемы аргументов и результатов tools как стабильный публичный контракт, версионируем breaking-изменения вместо тихого изменения структуры, делаем каждую операцию идемпотентной насколько возможно (особенно для долгих вызовов и ретраев), заранее закладываем таймауты, отмену и ограничение длительности выполнения тулов, учитываем, что MCP-хост может запускать много параллельных вызовов, проектируем сервер по сути статeless: состояние сессии минимальное, всё важное состояние хранится во внешних системах, явно разделяем слой протокола (JSON-RPC/stdio/HTTP) и слой доменных операций, выстраиваем порты и адаптеры между MCP-контрактом и инфраструктурой, даём каждому модулю сервера одну чёткую ответственность, избегаем god-service который знает и делает всё, программируем против абстракций (интерфейсы репозиториев, клиентов API, провайдеров секретов) а не против конкретных SDK, внедряем зависимости через конструктор/фабрику, не используем скрытые синглтоны и сервис-локаторы, явно прокидываем контекст запроса (user, workspace, permissions, correlation id) через все слои, проектируем авторизацию и ограничения доступа на уровне MCP-сервера (какие ресурсы, какие методы, какие параметры разрешены), реализуем принцип наименьших привилегий к внешним системам (отдельные ключи/аккаунты с минимальными правами), рассматриваем prompt injection и data-exfiltration как ключевые угрозы и фильтруем/ограничиваем то, что сервер отдаёт модели, не даём модели произвольно читать всё подряд, а только явно разрешённые ресурсы, шифруем секреты и конфигурацию, полностью выносим их из кода, используем единый формат конфигурации для всех MCP-серверов (env + config-файлы), логируем все вызовы tools/resources с correlation id, но не логируем чувствительные данные целиком, добавляем метрики по латентности, ошибкам и частоте вызовов на уровень MCP-операций, делаем health-checks и простые диагностические методы для наблюдаемости, проектируем структуру каталогов и модулей сервера так, чтобы она отражала слои: протокол (transport), приложение/use-cases, домен, инфраструктура, чётко разделяем DTO для MCP-контракта и внутренние доменные модели, не тащим наружу внутренние типы, проектируем server-level API минимальным и целевым: лучше несколько маленьких точных tools чем один «universalTool», избегаем chatty-стиля с кучей мелких вызовов, предпочитаем осмысленные coarse-grained операции, проектируем обработку ошибок как часть публичного контракта (свои коды/типы ошибок, понятные сообщения для модели), различаем бизнес-ошибки и технические сбои, для кросс-MCP сценариев и композиций держим контракты максимально простыми и стабильными, с самого начала думаем о многосервисной картине: несколько MCP-серверов под разные домены вместо одного монстра, стандартизируем кросс-серверные вещи (логирование, метрики, трассировка, формат ошибок, правила версионирования), упаковываем каждый сервер как автономный сервис (container, своя конфигурация, свои лимиты ресурсов), делаем запуск/остановку/обновление сервера полностью автоматизируемыми, интегрируем тесты в CI для каждого MCP-сервера отдельно, покрываем доменную логику и маппинг MCP-DTO↔доменные модели юнит-тестами, а работу с реальными внешними системами — интеграционными тестами, обеспечиваем возможность эволюции: новые tools и ресурсы добавляются без ломки старых, старые версии помечаем как deprecated и удаляем по плану, держим документацию по каждому MCP-серверу рядом с кодом (описание tools/resources, схемы, ограничения, примеры), ограничиваем стек технологий для всех MCP-серверов, чтобы упростить сопровождение, проектируем архитектуру MCP-серверов так, чтобы новому разработчику было легко зайти: простые слои, явные границы, минимальная магия, и на всех уровнях поддерживаем строгую разделённость домена, протокола MCP и инфраструктуры, явные контракты и слабую связность между модулями и серверами
start from defining the business capabilities and data it actually makes sense to expose via MCP, design each MCP server around a single clear bounded context and one primary external system rather than a random bag of tools, define the domain model of what the server provides to the model (resources, tools, prompts) before picking SDKs and transports, separate the server’s domain logic from the MCP protocol and from concrete external APIs, encapsulate external API details inside adapters and keep them out of tool/resource schemas, design argument and result schemas for tools as a stable public contract and version them when you need breaking changes, make operations as idempotent as possible to survive retries and long-running calls, build in timeouts, cancellation and execution limits for every tool, assume the MCP host may call many tools in parallel and design for concurrency, keep the server essentially stateless and store important state in external systems, explicitly separate the protocol layer (JSON-RPC / stdio / HTTP) from the application/use-case layer, build ports-and-adapters boundaries between MCP contracts and infrastructure, give each module in the server a single focused responsibility and avoid god-objects that know everything, code against abstractions (interfaces for repositories, API clients, secret providers) instead of concrete SDKs, inject dependencies via constructors/factories instead of hidden singletons or service locators, explicitly propagate request context (user, workspace, permissions, correlation id) through all layers, enforce authorization and access limits at the MCP-server boundary (which resources, which methods, which parameters are allowed), apply least privilege for external systems with dedicated keys/accounts and minimal permissions, treat prompt injection and data exfiltration as first-class threats and strictly control what data the server can return to the model, expose only explicitly allowed resources instead of broad arbitrary access, keep all secrets and configuration out of code and store them encrypted, use a unified configuration approach for all MCP servers (env + config files + secret store), log all tool/resource invocations with correlation IDs while avoiding full dumps of sensitive payloads, add metrics for latency, errors and call frequency per MCP operation, implement health checks and lightweight diagnostic endpoints for observability, structure the project so directories and modules reflect layers (transport/protocol, application/use-cases, domain, infrastructure), separate DTOs for MCP contracts from internal domain models and avoid leaking internal types through public schemas, design the server-level API to be minimal and purposeful, prefer several small precise tools to one giant “do-everything” tool, avoid overly chatty sequences of tiny calls and favor meaningful coarse-grained operations, treat error handling as part of the public contract with clear error types/codes and messages that are useful to the model, distinguish domain/business errors from technical failures, keep cross-MCP scenarios and compositions simple by keeping each server’s contract small and stable, plan from the start for multiple MCP servers instead of a single monster server, group servers by domain and external system to keep them cohesive, standardize cross-server concerns (logging format, metrics, tracing, error shape, versioning rules), package each server as an autonomous service (e.g. container) with its own configuration and resource limits, make starting, stopping and upgrading a server fully automatable, give each MCP server its own CI pipeline with tests and checks, cover domain logic and MCP-DTO↔domain mapping with unit tests, cover real integrations with external systems via integration tests, evolve servers safely by adding new tools/resources without breaking existing ones, deprecate old contracts explicitly and remove them according to a plan, keep documentation per MCP server close to the code (what tools/resources exist, schemas, limits, examples), limit the technology stack across all MCP servers to reduce cognitive load and operational complexity, design the architecture of MCP servers so new developers can onboard quickly (simple layers, explicit boundaries, minimal magic), and at every level maintain strict separation between domain, MCP protocol and infrastructure, explicit contracts, weak coupling between modules and servers, and clear, testable responsibilities.
### telegram bots
начинаем не с команд а с домена бота и пользы для пользователя, формулируем ключевые сценарии: какие роли у бота, какие потоки диалога, какие данные он читает и меняет, описываем доменную модель (пользователь, чат, сессия, задачи/сущности предметной области), разделяем слои: домен, application/use-cases, инфраструктура, интеграции, выделяем Telegram как один из адаптеров на краю системы, не тащим типы и структуры Telegram в домен, маппим Update/Message/CallbackQuery в свои DTO и команды, строим архитектуру вокруг use-case хендлеров а не вокруг Telegram команд, даём каждому хендлеру одну чёткую ответственность, избегаем «universalHandler» который делает всё, отделяем парсинг апдейта от принятия бизнес-решения, держим доменную логику независимой от Telegram SDK и конкретных HTTP-клиентов, программируем против абстракций интерфейсов для отправки сообщений, хранения состояния и доступа к данным, внедряем зависимости в слой use-cases, избегаем глобальных ботов, синглтонов и скрытых сервис-локаторов, явно прокидываем контекст запроса (user id, chat id, язык, права, correlation id) во все слои, проектируем сценарии диалога как конечные автоматы или шаги с явной моделью состояния, не храним сложное состояние в памяти процесса если бот масштабируется горизонтально, выделяем хранение состояния в отдельное хранилище (БД, cache, key-value) с чёткими контрактами, разделяем краткосрочный диалоговый контекст и долгосрочные данные домена, избегаем «магии» с огромным switch по командам, используем маршрутизацию по intent/типу команды и текущему состоянию, отдельно описываем формат команд, кнопок и callback data как стабильный контракт, версионируем callback data и payload’ы если меняем структуру, инкапсулируем форматирование сообщений и клавиатур в отдельный слой presenter/formatter, не смешиваем бизнес-логику с HTML/Markdown разметкой прямо в use-cases, делаем шаблоны сообщений и текстов переиспользуемыми и локализуемыми, сразу закладываем поддержку нескольких языков и часовых поясов, выносим все токены, URL и ключи в конфигурацию и секрет-хранилища, никогда не коммитим токен бота в репозиторий, поддерживаем отдельные конфиги на окружения (dev/stage/prod), явно выбираем способ доставки апдейтов (webhook или long polling) как инфраструктурную деталь, оборачиваем работу с Telegram API в адаптер с ретраями, таймаутами и логированием, учитываем лимиты и rate limit Telegram, проектируем throttling и очереди для массовых рассылок, делаем операции по возможности идемпотентными, учитываем, что один и тот же update может прийти дважды, обрабатываем ошибки Telegram как часть публичного контракта адаптера, различаем бизнес-ошибки (нельзя выполнить команду) и технические (падает API, нет сети), не даём падать всему процессу на одном неудачном апдейте, оборачиваем обработку каждого update в защитную оболочку с try/catch и логированием, логируем ключевые события: входящие апдейты, критичные команды, ошибки, интеграции, добавляем метрики по количеству апдейтов, ошибкам, латентности, строим health-check и простые диагностические эндпоинты, проектируем структуру каталогов отражая архитектуру: domain, application, infrastructure, telegram-адаптер, конфиг, не смешиваем файлы по принципу «по типу фреймворка», держим тесты рядом с доменном и use-cases, покрываем доменную логику юнит-тестами без Telegram, Telegram-адаптер тестируем интеграционно и через контрактные тесты, используем тестовые двойники для внешних API, продумываем безопасность: валидация входных данных, защита от инъекций в callback data, ограничение опасных операций ролями/правами, не шлём в лог чувствительные данные, разделяем приватные чаты и групповые сценарии, явно обрабатываем различия между типами чатов и их правами, проектируем бота так, чтобы можно было добавить новых команд и сценариев без переписывания ядра, выделяем расширяемые точки — регистрацию новых хендлеров, стейт-машин, шаблонов сообщений, делаем публичное API модулей минимальным и стабильным, избегаем god-сервисов типа BotService который знает обо всём на свете, делаем бот как тонкий слой над хорошо спроектированным приложением/доменом, чтобы его же логику можно было использовать из других интерфейсов (web, CLI, MCP, microservice), автоматизируем сборку, деплой и миграции, поддерживаем возможность отката версии бота, проектируем систему так, чтобы новый разработчик по структуре каталогов и контрактам быстро понял, что где, и во всём стеке бота поддерживаем строгие границы слоёв, явные контракты, слабую связность и тестируемость.
start from the bot’s domain and the value it brings to users, define the key scenarios and conversation flows before thinking about commands, model the core domain concepts (user, chat, session, domain entities) explicitly, structure the system into domain, application/use-case, infrastructure, and telegram-adapter layers, treat Telegram as just one adapter at the edge of the system, avoid pulling Telegram types and DTOs into the domain model, map Update / Message / CallbackQuery to your own commands and DTOs, design architecture around use-case handlers instead of raw Telegram commands, give each handler a single clear responsibility, avoid a single giant “universal handler” that does everything, separate update parsing from business decision-making, keep business logic independent of the Telegram SDK and HTTP client details, code against abstractions for message sending, state storage, and data access, inject these dependencies into use-case layers instead of using globals, avoid global bot instances, singletons, and service locators, explicitly pass request context (user id, chat id, locale, permissions, correlation id) through all layers, model conversational flows and wizards as finite state machines or explicit step models, don’t store complex conversational state only in process memory if you plan horizontal scaling, keep state in a dedicated storage (DB, cache, key-value) with clear contracts, separate short-lived conversational context from long-lived domain data, avoid huge switch/if chains on commands scattered across the codebase, centralize routing by intent/command type and current state, define the format of commands, buttons, and callback data as a stable contract, version callback payloads if you change their structure, encapsulate message formatting and keyboards in a presenter/formatter layer, do not mix business logic with HTML/Markdown layout in use-cases, make message templates reusable and localizable, plan for multiple languages and time zones from the start, move all tokens, URLs, and secrets out of code into configuration and secret stores, never commit the bot token to the repository, maintain separate configs per environment (dev/stage/prod), treat choice of delivery mechanism (webhook vs long polling) as an infrastructure concern, wrap Telegram API access in an adapter with retries, timeouts, and logging, respect Telegram rate limits and design throttling and queues for bulk sends, make operations as idempotent as possible and handle duplicate updates safely, treat Telegram API errors as part of the adapter’s public contract, distinguish between business errors (command cannot be executed) and technical errors (network/API failure), prevent a single bad update from crashing the whole process, wrap each update handling in a safe boundary with proper error handling and logging, log key events such as incoming updates, critical commands, and integration calls, add metrics for update throughput, error rates, and handler latency, expose health checks and lightweight diagnostic endpoints for operations, design the folder and module structure to reflect the architecture (domain, application, infrastructure, telegram) rather than framework accidents, keep tests close to domain and use-cases, cover domain logic with unit tests that don’t depend on Telegram, test the Telegram adapter via integration and contract tests, use test doubles for external APIs and storages, design for security from the start with strict validation of incoming data, protect against injection and malformed callback data, enforce roles/permissions for sensitive operations, avoid logging sensitive information, separate private chat flows from group chat flows explicitly, handle the differences between chat types and their capabilities, design the bot so new commands and flows can be added by registering new handlers and states, not by rewriting the core, define clear extension points for new handlers, state machines, and message templates, keep public module APIs minimal and stable, avoid “BotService” god-objects that know everything, make the bot a thin interface over a well-designed application/domain layer so the same logic could be reused from web, CLI, MCP, or microservices, automate build, deployment, and migrations, support safe rollback of bot versions, design the system so that a new developer can understand the structure and contracts quickly, and throughout the bot keep strict separation of concerns between layers, explicit contracts, weak coupling, and high testability.
#### Telegram Aiogram
начинаем не с aiogram и команд, а с домена бота и пользы для пользователя, формулируем ключевые сценарии и флоу диалогов, выделяем основные сущности домена (пользователь, чат, сессия, предметные объекты), строим архитектуру по слоям: domain, application/use-cases, infrastructure, telegram/aiogram-адаптер, рассматриваем Telegram и aiogram как инфраструктурный слой на границе системы, не тянем типы Message, CallbackQuery, Update и прочие объекты aiogram в доменную модель, маппим апдейты в свои команды/DTO на границе слоя, строим систему вокруг use-case / application-хендлеров, а не вокруг сырых Telegram-команд, даём каждому use-case одну чёткую ответственность, избегаем одного огромного хендлера «на всё», отделяем разбор апдейта и роутинг (filters, routers) от бизнес-логики, держим логику сценариев независимой от aiogram и деталей Telegram API, используем абстракции для отправки сообщений, работы с хранилищем и внешними сервисами, не привязываем домен к конкретным clients/ORM/SDK, внедряем зависимости в use-case слои через конструкторы/фабрики, не используем скрытые синглтоны, глобальные Bot/Dispatcher как «магические» глобальные точки доступа, инициализируем бот, диспетчер, хранилища и клиенты во входной точке приложения (composition root) и дальше явно прокидываем их в нужные модули, проектируем маршрутизацию через Router’ы по доменным модулям (отдельные роутеры на отдельные подсистемы, разделяем команды, callback-и, inline и т.п.), избегаем одного большого router с сотнями хендлеров, используем фильтры aiogram для декларативной маршрутизации, но держим фильтры как тонкий слой над доменными условиями, выносим общие кросс-срезы (логирование, аутентификация, загрузка пользователя, локаль, трейсинг, rate limiting) в middlewares, не размазываем их по всем хендлерам руками, моделируем сложные диалоги через FSM/машину состояний aiogram, но состояние описываем доменными терминами, а не «State1/State2», разделяем краткоживущее диалоговое состояние (шаг формы, выбор варианта) и долгоживущие сущности домена (заказы, задачи, профили и т.п.), не рассчитываем только на in-memory FSM, если бот планируется к горизонтальному масштабированию — используем внешнее хранилище состояний (Redis, БД) через адаптер, чётко определяем, какие данные хранятся в FSM, какие — в доменной БД, описываем формат команд, callback data и payload-ов как стабильный контракт, используем структурированный callback data (например, action:entity:id) и версионируем его при изменениях, не шифруем смысл в непонятные строки-UUID без причины, выносим форматирование сообщений, клавиатур и inline-кнопок в отдельный слой presenters/formatters, не смешиваем бизнес-логику с HTML/Markdown прямо в хендлерах, храним тексты и шаблоны отдельно, закладываем локализацию: используем i18n (aiogram-i18n/любой другой подход), не хардкодим тексты в коде, учитываем таймзоны и локали пользователя при форматировании дат/времени, выносим токен бота, URL-ы, ключи, флаги и прочую конфигурацию в env и конфиг-файлы, ни при каких условиях не коммитим токен бота в репозиторий, делаем отдельные конфигурации для dev/stage/prod, учитываем, что выбор между webhook и long polling — инфраструктурное решение: оборачиваем запуск/конфигурацию вебхука в отдельный слой, не смешиваем это с доменом, оборачиваем Telegram API в свой сервис/адаптер поверх Bot, добавляем туда ретраи, таймауты, логирование и обработку rate limits, дизайн операций делаем максимально идемпотентным, учитываем, что один update_id может прилететь повторно, явно обрабатываем ошибки Telegram и коды ответов, различаем бизнес-ошибки (пользователь сделал недопустимое действие по доменным правилам) и технические (упало API, таймаут, сеть), не даём упасть всему приложению при исключении в одном хендлере, оборачиваем обработку каждого апдейта try/except-оболочкой и логированием, используем middlewares для логирования и трейсинга входящих апдейтов и исходящих запросов к Telegram и внешним системам, логируем ключевые события: новые пользователи, важные команды, неудачные попытки выполнения доменных операций, добавляем метрики: количество апдейтов, ошибок по типам, латентность хендлеров, частоту вызова отдельных use-cases, делаем health-check эндпоинты для проверки зависимостей (БД, внешние API) и самого процесса, проектируем структуру проекта так, чтобы она отражала архитектуру: отдельные пакеты/модули для domain, usecases (application), infra (БД, внешние сервисы, репозитории), telegram (routers, handlers, middlewares, formatters), не строим структуру только вокруг handlers.py и keyboards.py, держим хендлеры максимально тонкими: парсим вход, передаём команду в use-case, получаем результат, форматируем ответ, покрываем доменную логику и use-cases модульными тестами без aiogram, Telegram-адаптер и интеграции с внешними API проверяем интеграционными/контрактными тестами, используем fixtures и test storages, продумываем безопасность: валидируем входящие данные (особенно callback data, user input, inline queries), не доверяем содержимому апдейта, ограничиваем опасные операции по ролям/правам, отделяем сценарии в приватных чатах от сценариев в группах/супергруппах, учитываем различия в поведении и правах, не логируем и не показываем стеки/детали ошибок пользователю, но даём пользователю понятные сообщения об ошибке/недоступности функционала, проектируем бота расширяемым: новые команды и флоу добавляются через новые routers/handlers/use-cases, а не через переписывание существующих монолитных файлов, выделяем явные точки расширения (регистрация роутеров, новое состояние, новый formatter), избегаем god-объектов вроде одного огромного BotService/Core на всё, делаем бота тонким интерфейсом над хорошо спроектированным приложением/доменом, чтобы при необходимости ту же логику можно было вызвать из других интерфейсов (web, CLI, микро-сервис, MCP), автоматизируем сборку, деплой и миграции (Docker, CI/CD, alembic/аналог для БД), закладываем возможность безопасного отката версии, фиксируем архитектурные решения в коротких ADR, чтобы команда понимала «почему так», проектируем всё так, чтобы новому разработчику было легко зайти: читаемая структура каталогов, понятные имена, явные контракты между слоями, минимум «чёрной магии», и во всём коде бота на aiogram поддерживаем строгие границы слоёв, явные контракты, слабую связность, тестируемость и предсказуемое поведение.
#### WPF C#
начинаем не с XAML и контролов, а с домена приложения и пользовательских сценариев, формулируем, какие задачи решает десктоп-приложение и какие сущности домена в нём есть, строим архитектуру по слоям: domain (модели и бизнес-правила), application/services (use-cases, оркестрация), infrastructure (БД, файлы, сети), presentation (WPF UI, View + ViewModel), используем MVVM как основной паттерн, чётко разделяем View, ViewModel и Model, избегаем бизнес-логики в code-behind и в XAML-триггерах, держим code-behind максимально тонким (инициализация, мелкие UI-хаки, которые нельзя выразить иначе), всё поведение и состояние экрана выносим во ViewModel, программируем против абстракций: ViewModel не знает о конкретных контролах и окнах, ViewModel общается с внешним миром через сервисы и интерфейсы, используем INotifyPropertyChanged и биндинги как основной механизм связи UI и ViewModel, избегаем ручного FindName и прямого обращения к контролам из логики, строим взаимодействие через привязки свойств, ICommand и события/мессенджеры, определяем ViewModel’ы вокруг экранов и пользовательских сценариев, а не вокруг таблиц БД и форм, даём каждому ViewModel одну чёткую ответственность, избегаем гигантских «MainViewModel» и «GodViewModel», декомпозируем сложные экраны на композицию мелких View + ViewModel, выделяем общие компоненты и переиспользуем их, навигацию (открытие окон, страниц, диалогов) инкапсулируем в отдельный сервис навигации, ViewModel не создаёт напрямую окна и не вызывает Show()/ShowDialog(), навигация строится через команды и абстракции, внедряем зависимости в ViewModel через конструктор и DI-контейнер, а не создаём сервисы внутри через new, не используем сервис-локаторы и глобальные синглтоны, конфигурацию, подключения и пути выносим в настройки и конфиг-файлы, не хардкодим их в коде, держим доменную логику чистой и независимой от WPF и UI-фреймворка, любые обращения к БД, файловой системе, сети, диалогам, печати выносим в адаптеры и инфраструктурные сервисы, ViewModel обращается к ним через интерфейсы, разделяем модели домена и модели представления (DTO/ViewModel), не тянем напрямую сущности ORM в биндинги, проектируем валидацию данных на уровне ViewModel и домена (IDataErrorInfo/INotifyDataErrorInfo), не кладём всю валидацию только в UI, используем команды (ICommand, Relay/DelegateCommand) вместо обработки кликов в code-behind, вся реакция на действие пользователя живёт во ViewModel, продумываем асинхронность: долгие операции выполняем через async/await и фоновые задачи, никогда не блокируем UI-поток Thread.Sleep или Task.Result, показываем индикаторы загрузки и не даём пользователю «подвешивать» приложение, используем ObservableCollection и другие коллекции, поддерживающие уведомления, для списков в UI, не обновляем UI-коллекции вручную без уведомлений, инкапсулируем доступ к БД и файлам в репозитории/сервисы, не пишем SQL/IO прямо в ViewModel, отделяем шаблоны стилей, ресурсов и визуальных элементов в ResourceDictionary и отдельные XAML-файлы, не превращаем App.xaml и главное окно в свалку ресурсов, централизуем стили и тему приложения, закладываем локализацию: строки интерфейса выносим в ресурсы, не хардкодим текст в XAML и коде, следим за тем, чтобы ViewModel не зависел от конкретного DI-контейнера, логгера, ORM — используем интерфейсы и адаптеры, добавляем логирование ключевых действий и ошибок в инфраструктурном слое, не показываем пользователю «сырае» исключения, а даём дружелюбные сообщения, обрабатываем ошибки асинхронных операций и не глушим их в пустых catch, проектируем структуру проектов так, чтобы она отражала архитектуру: отдельные сборки/проекты для домена, приложений/сервисов, инфраструктуры и WPF-UI, не смешиваем всё в одном гигантском проекте, пишем юнит-тесты для доменных моделей и сервисов, ViewModel тестируем отдельно от реального WPF, подменяя сервисы тестовыми двойниками, минимизируем логику, завязанную именно на UI-поток, чтобы упростить тестирование, продумываем старт приложения: в App/bootstrapper создаём контейнер, регистрируем сервисы, ViewModel и View, настраиваем маппинг View ↔ ViewModel, не размазываем инициализацию по всем углам, для сложных приложений используем чёткий подход к модульности (модули/области, отдельные части UI и домена), избегаем плотной связности модулей, общение между модулями строим через события, сообщения или сервисы, а не через прямые ссылки, следим за производительностью: тяжёлые визуальные эффекты, сложные шаблоны и триггеры используем осознанно, профилируем, не оптимизируем «на глаз», используем VirtualizingStackPanel и виртуализацию для длинных списков, управляем ресурсами (подписки на события, таймеры, потоки), не допускаем утечек памяти из-за незавершённых подписок и сильных ссылок, явно освобождаем ресурсы, когда окна и ViewModel больше не нужны, документируем ключевые архитектурные решения (паттерн MVVM, подход к навигации, DI, структуру слоёв) хотя бы в виде кратких ADR, придерживаемся одного стиля кодирования и XAML-разметки в команде, автоматизируем сборку и проверку (анализаторы, StyleCop, Roslyn-анализаторы), проектируем WPF-приложение так, чтобы новому разработчику было понятно, где домен, где инфраструктура, где View и ViewModel, и во всём коде строго поддерживаем разделение ответственности, явные границы между слоями, слабую связность и тестируемость.
## c++
начинаем не с классов и шаблонов, а с домена и архитектуры системы, формулируем бизнес-задачи и ключевые сценарии, выделяем доменную модель и границы контекстов, разделяем слои: домен (чистые модели и правила), application/use-cases (оркестрация), infrastructure (БД, сеть, файловая система, ОС), presentation/CLI/GUI, держим доменную логику максимально независимой от конкретных библиотек, фреймворков и платформенных деталей, программируем против абстракций — интерфейсы через чисто виртуальные классы и концепты, а не против конкретных реализаций, используем композицию вместо наследования и иерархий «на всё», выделяем один чёткий повод для изменения у каждого компонента (SRP), избегаем god-объектов и «менеджеров всего», проектируем зависимости направленными и как можно более слабыми (DIP), внедряем зависимости через конструктор/фабрики/шаблоны, а не через глобальные синглтоны, минимизируем и изолируем глобальное состояние, не плодим extern и «магические» глобальные переменные, строим API модулей минимальными и стабильными, скрываем детали реализации через pImpl, private-headers и внутренние namespace, следим за границей заголовков и реализаций: интерфейсы и контракты в .h, детали в .cpp, избегаем тяжёлых include в публичных заголовках, используем forward-declaration там где возможно, уменьшаем время сборки, проектируем управление ресурсами через RAII — никакого голого new/delete и malloc/free в прикладном коде, используем std::unique_ptr, std::shared_ptr, std::optional, std::variant и контейнеры стандартной библиотеки, явно договариваемся об владении ресурсами, не допускаем «висячих» указателей и непонятных жизненных циклов, избегаем «сырого» указателя как владельца ресурса, используем его только как невладеющую ссылку, продумываем модель ошибок: где используются исключения, где std::expected/std::optional/коды возврата, не смешиваем стили без необходимости, считаем ошибки частью публичного контракта, явно описываем, какие функции могут бросать и чем, не бросаем исключения через границы модулей/библиотек без чёткой договорённости, проектируем многопоточность как отдельный архитектурный аспект: выбираем модель (actor, job system, thread pool, task-based), защищаем общий доступ через std::mutex, std::shared_mutex, std::atomic, избегаем ручной «дикий» работы с потоками без абстракций, минимизируем shared-state и используем иммутабельные структуры там где возможно, разделяем CPU-интенсивные и IO-операции, изолируем низкоуровневый код в инфраструктурных модулях (работа с файловой системой, сетью, ОС-API, графикой), не смешиваем бизнес-правила и системные вызовы в одних классах, проектируем API модулей так, чтобы их можно было тестировать без настоящего железа/сети, используем интерфейсы и тестовые двойники, пишем модульные тесты на доменные правила и чистые функции, покрываем интеграционными тестами стыки модулей и адаптеры, структуру каталогов строим по архитектуре, а не по «типа файла» — domain/, app/, infra/, ui/, lib/, а внутри — по bounded context и подсистемам, используем понятные имена вместо абстрактных Manager, Helper, Utils, документируем контракты классов и функций на уровне интерфейсных заголовков, не перегружаем код комментариями вместо улучшения дизайна, осознанно используем шаблоны: выносим обобщённую логику в шаблоны там где это действительно нужно, но не усложняем читаемость ради микро-обобщения, держим метапрограммирование и template magic изолированными и хорошо задокументированными, отделяем публичный интерфейс библиотеки от её реализации, учитываем ABI-и API-стабильность, версионируем библиотеки и протоколы взаимодействия, следим за UB: пишем код, который не опирается на неопределённое или не заданное стандартом поведение, включаем строгие предупреждения компилятора и тритаем их как ошибки, используем статический анализ, линтеры и санитайзеры (ASan/UBSan/TSan) как часть CI, планируем сборку и деплой как часть архитектуры: CMake/meson и др. как инфраструктурный слой, а не «как получится», разбиваем решение на несколько целей/библиотек, чтобы отразить архитектурные границы, рассматриваем кроссплатформенность заранее: абстрагируем доступ к ОС и платформенным API, изолируем платформоспецифичный код за интерфейсами, думаем о производительности через архитектуру данных: data-oriented дизайн, локальность данных, минимизация аллокаций, но не жертвуем понятностью и корректностью раньше времени, оптимизируем только после измерений и профилирования, проектируем так, чтобы новый разработчик смог по структуре проекта и заголовкам понять, где домен, где инфраструктура, какие зависимости допустимы, а какие — нет, и во всём C++-коде держим строгие границы модулей, явные контракты, продуманное владение ресурсами, минимизацию глобального состояния, тестируемость и эволюционную расширяемость.
#### www services
начинаем не с фреймворка и роутов, а с домена веб-сервиса и требований, формулируем бизнес-цели и ключевые API-сценарии, явно описываем, какие ресурсы и операции предоставляет сервис, проектируем архитектуру от домена и use-cases, а не от контроллеров, разделяем слои: domain (модели и правила), application/use-cases (команды/queries), infrastructure (БД, очереди, внешние API), presentation/web (HTTP-слой), считаем HTTP просто транспортом на границе системы, не тащим HTTP-детали в домен, маппим запросы/ответы в свои DTO на границе web-слоя, строим контроллеры/handlers тонкими: парсим запрос, валидируем, вызываем use-case, маппим результат в ответ, не пишем бизнес-логику внутри контроллера, используем явные модели команд и запросов (CreateXCommand, GetXQuery) вместо расползания параметров, разделяем domain-модели и transport-модели (DTO), не тянем сущности ORM напрямую в JSON-ответы, валидируем входные данные на границе web-слоя и дополнительно на уровне домена (инварианты), не полагаемся только на UI/клиента, проектируем API-контракты осознанно: ресурсы, методы, коды ответов, структуры ошибок, версионируем публичные API и не ломаем их молча, проектируем стабильные схемы JSON/Protobuf/GraphQL-типов и эволюционируем их добавлением, а не изменением поля, используем явную структуру ошибок (код, тип, сообщение, детали), не бросаем наружу stacktrace и внутренние детали, отделяем бизнес-ошибки (валидация, доменные ограничения) от технических (таймаут, БД упала), логируем ошибки с контекстом, но не раскрываем секреты в логах и ответах, программируем против абстракций: домен и use-cases зависят от интерфейсов репозиториев, клиентов внешних сервисов и т.п., а не от конкретных ORM/HTTP-клиентов, внедряем зависимости через DI, избегаем синглтонов и сервис-локаторов с глобальным состоянием, выносим конфигурацию (строки подключения, URL, ключи, флаги) за пределы кода, используем окружение и конфиг-файлы, никогда не храним секреты в репозитории, продумываем модель безопасности с самого начала: аутентификация (tokens/OAuth/JWT и т.п.), авторизация (роли, права, проверки на уровне домена и маршрутов), минимизация прав у технических аккаунтов, валидируем и санитизируем входные данные, учитываем угрозы (SQL-инъекции, XSS через отражение, CSRF где релевантно, SSRF, RCE), минимизируем количество информации об ошибке, которую видит клиент, проектируем rate limiting, throttling и защиту от DoS на границе сервиса, чётко описываем модель идентификации пользователя/клиента и прокидываем её через все слои как часть контекста, проектируем кэширование осознанно: HTTP-кэш заголовками, приложение/БД-кэш, не превращаем кэш в источник неконсистентности, разделяем операции чтения и записи там, где это даёт выигрыш (CQRS-подход), используем облегчённые модели для чтения и строгие доменные для записи, проектируем транзакционные границы: что делаем в одной БД-транзакции, где допускаем eventual consistency, изолируем работу с БД в репозиториях/юнитах работы с данными, не пишем SQL напрямую в контроллерах, следим за миграциями схем: используем миграции и стратегии обратной совместимости, продумываем взаимодействие с внешними сервисами: оборачиваем HTTP-клиентов в адаптеры, добавляем таймауты, ретраи с backoff, circuit breaker, задаём требования к идемпотентности операций при повторных запросах, используем корреляционные id для трейсинга запросов через сервисы, логируем ключевые события и метрики (latency, RPS, коды ответов, ошибки по типам), добавляем health-checks и readiness/liveness-эндпоинты, строим наблюдаемость: структурированные логи, метрики, трассировка (distributed tracing), проектируем структуру каталогов так, чтобы она отражала архитектуру (домен/приложение/инфраструктура/web), а не просто «controllers/models/helpers», избегаем god-модулей и «Manager/Helper/Util» без контекста, пишем модульные тесты на доменный слой и use-cases без HTTP и БД, проверяем web-слой через интеграционные/контрактные тесты, используем тестовые doubles для внешних зависимостей, интегрируем тесты в CI, учитываем производительность архитектурно: разумные индексы, пагинация, ограничения размерности запросов/ответов, стриминг там, где нужно, но не делаем преждевременных оптимизаций без измерений, закладываем масштабирование: сначала простой масштабируемый модульный монолит, только потом, при реальной необходимости, деление на сервисы, проектируем деплой и окружения как часть архитектуры (контейнеры, оркестратор, конфиг-менеджмент), обеспечиваем возможность безопасного отката и поэтапного раската (blue-green, canary), фиксируем архитектурные решения и контракты API в ADR и спецификациях, держим документацию API рядом с кодом (OpenAPI/GraphQL schema), поддерживаем единый стиль кодирования и форматирования, и на всех уровнях веб-сервиса соблюдаем строгие границы слоёв, явные контракты, слабую связность между модулями и возможность независимо развивать, тестировать и деплоить части системы.
### wwww servcies for frontend
начинаем не с React/Vue и красивых компонентов, а с домена фронтенда и задач пользователя, формулируем ключевые пользовательские сценарии и потоки, определяем, какие данные нужны на клиенте и в каком виде, строим архитектуру от use-cases и состояния, а не от страниц и роутов, разделяем слои: domain (модели и бизнес-правила), application/state (use-cases, состояние, эффекты), infrastructure (API-клиенты, storage, адаптеры), presentation (компоненты UI), считаем backend/web-сервисы просто внешней системой и источником данных, не тащим DTO бэкенда напрямую в UI-компоненты, маппим транспортные модели в фронтовые доменные модели на границе слоя данных, проектируем API-клиент как отдельный модуль, инкапсулируем детали HTTP, URL и заголовков, не размазываем fetch/axios по всем компонентам, отделяем бизнес-логику и правила отображения от конкретного фреймворка, стараемся делать большую часть логики фреймворк-агностичной, даём каждому модулю и хуку одну чёткую ответственность, избегаем god-компонентов и контейнеров, которые знают «про всё приложение», строим дерево компонентов от контейнеров/страниц к презентационным компонентам, держим презентационные компоненты максимально тупыми и переиспользуемыми, не привязываем их к глобальному состоянию и API, выносим управление состоянием в application-слой (state managers, hooks, service-слой), минимизируем глобальное состояние и храним его только для действительно общих вещей (auth, user, настройки, кэш ключевых данных), всё остальное состояние держим локально в компонентах или feature-слоях, явно разделяем server state (данные из API) и UI state (выборы, фильтры, модалки), не смешиваем их в одной огромной структуре, используем специализированные решения для работы с server state (query-клиенты, кэш, нормализация) вместо самописного хаоса, проектируем контракты запросов/ответов в связке с backend, держим типы/схемы рядом с кодом, валидируем данные на входе, не доверяем бэкенду вслепую, оборачиваем все обращения к API в единый слой (services/repositories), добавляем туда обработку ошибок, ретраи, отмену запросов и таймауты, не повторяем один и тот же запросный код по всему проекту, явно обрабатываем loading/error/success-состояния, не прячем ошибки, но и не показываем пользователю стек-трейсы, проектируем UI с учётом пустых состояний, ошибок и медленных ответов, не рассчитываем, что «всё всегда быстро и успешно», выносим логику маршрутизации (router) в отдельный слой, роуты описываем как карту экранов и доменных разделов, избегаем сильной завязки компонентов на конкретные URL, используем конфигурационный подход к навигации, проектируем дизайн-систему и библиотеку компонентов: кнопки, инпуты, layout, типографика — как отдельный слой, не копипастим стили от компонента к компоненту, храним стили централизованно (design tokens, theme), закладываем поддержку тёмной темы и адаптивности с самого начала, думаем о доступности (a11y): правильные семантические теги, aria-атрибуты, фокус, клавиатурная навигация, не полагаемся только на мышь и красивый вид, выносим кросс-каттинги (логирование, аналитика, error boundary, feature flags) в отдельные слои/обёртки, не вставляем аналитику и логи во все компоненты хаотично, планируем международизацию: тексты через i18n/ресурсы, без хардкода строк внутри компонентов, учитываем форматы дат/чисел и локали, конфигурацию фронтенда (базовые URL API, ключи фич, режимы) выносим в env и конфиги, не хардкодим их в коде, отдельно настраиваем окружения dev/stage/prod, следим за границами между слоями: UI-компоненты не ходят напрямую в localStorage/SessionStorage/IndexedDB, а используют сервисы, не знают деталей хранения, проектируем обработку кеша и offline-режима при необходимости, не превращаем localStorage в «мини-бэкенд без правил», пишем тесты на критичную бизнес-логику и state-менеджмент, компонентов тестируем на уровне unit/интеграции для ключевых сценариев, не пытаемся покрыть всё сквозными E2E-тестами, но обязательно имеем несколько end-to-end сценариев для важнейших флоу, строим структуру проекта по архитектуре: domain, application/state, infrastructure, ui/features, а не только components/pages/utils, избегаем папок helpers и utils без контекста, называем модули по предметной области и роли, следим за зависимостями между feature-модулями, не допускаем циклических зависимостей и плотной связности, ограничиваем технологический стек: один основной фреймворк, один state-manager-подход, одна система стилизации, не вводим по три разных решения для одной задачи, думаем о производительности архитектурно: разбиваем бандл (code splitting), используем lazy-loading для тяжёлых страниц, мемоизацию для горячих участков, но не оптимизируем преждевременно без измерений, используем профилирование и метрики RUM, проектируем фронтенд так, чтобы его же доменную и application-логику можно было переиспользовать в других оболочках (desktop/WebView, mobile shell), держим связку frontend↔backend через явные контракты (OpenAPI/GraphQL schema), обновляем фронт по изменению схем, а не по догадкам, документируем ключевые архитектурные решения (выбор state-manager, структура слоёв, паттерн модулей, договорённости по API), поддерживаем единый код-стайл и линтеры/форматтеры как обязательную часть CI, и на всех уровнях фронтенда соблюдаем строгие границы слоёв, явные контракты, слабую связность модулей, предсказуемое управление состоянием и высокую тестируемость.
###Angular backend
начинаем не с Nest/Express и декораторов, а с домена сервиса и его API-обязанностей, формулируем бизнес-сценарии и ресурсы, которые реально должен отдавать backend, разделяем слои: domain (модели и правила), application/use-cases (команды/queries, оркестрация), infrastructure (БД, очереди, внешние API), web/transport (HTTP, WebSocket, GraphQL), рассматриваем Nest/Express только как транспортный слой и DI-рамку, не тянем HTTP-детали и декораторы (@Req, @Res, @Body) в домен, маппим запросы/ответы в DTO на границе контроллеров, делаем контроллеры максимально тонкими: валидируем и нормализуем вход, вызываем use-case, маппим результат в transport DTO, не пишем бизнес-логику прямо в контроллерах и middleware, строим архитектуру вокруг сервисов/хендлеров use-cases (CreateUser, UpdateOrderStatus, GetUserProfile), а не вокруг «универсальных» сервисов, даём каждому сервису и модулю одну чёткую ответственность, избегаем god-сервисов типа AppService, которые знают обо всём, явно разделяем domain-entity и persistence/ORM-entity, не протаскиваем сущности ORM напрямую в JSON-ответы и доменные правила, изолируем работу с БД через репозитории/порты, используем интерфейсы и адаптеры поверх ORM/клиентов (TypeORM, Prisma, Mongoose), проектируем репозитории как часть инфраструктуры, а не домена, используем DI Nest как механизм внедрения зависимостей, но не превращаем его в сервис-локатор: зависимости прокидываем через конструктор, держим зависимости направленными от верхних слоёв к абстракциям, не плодим статические синглтоны, выносим конфигурацию (строки подключения, секреты, URL, флаги) в env и конфиг-модули, ничего чувствительного не коммитим в репозиторий, проектируем валидацию на границе (class-validator, pipes) и дублируем ключевые инварианты в домене, разделяем transport DTO и доменные модели, не смешиваем, проектируем ошибки как часть публичного контракта: свои коды, типы (business/validation/technical), человеко-понятные сообщения, не вываливаем stacktrace наружу, используем exception filters и interceptors для унификации ошибок и логирования, думаем о безопасности с самого начала: аутентификация (JWT/OAuth/Session), авторизация (guards, policies, роли и permissions в домене), защита от типичных атак (SQL/NoSQL injection, XSS через отражение, CSRF, brute-force), минимизируем права у сервисных аккаунтов, логируем ключевые события и ошибки структурированно (interceptors, middleware), добавляем correlation id в каждый запрос, не логируем пароли, токены и чувствительные данные, включаем метрики (latency, RPS, ошибки по типам) и health-check endpoints, используем pipes/guards/interceptors как кросс-срезы (валидация, auth, rate limiting, кэширование) а не размазываем их руками в каждом контроллере, отделяем операции чтения и записи (CQRS) там, где это оправдано, используем отдельные query-handlers и простые модели для чтения, строгие доменные модели для записи, проектируем обработку внешних API через клиентские сервисы/адаптеры с таймаутами, ретраями и circuit breaker, делаем операции идемпотентными там, где возможны повторы, проектируем структуру модулей Nest так, чтобы она отражала архитектуру и bounded contexts (UserModule, OrdersModule, BillingModule) а не «misc», избегаем циклических зависимостей между модулями, выносим shared-модули только для действительно общих вещей (logger, config, auth), не превращаем shared в свалку, пишем модульные тесты для доменных сервисов и use-cases без Nest, контроллеры и интеграцию с БД и внешними сервисами покрываем интеграционными/контрактными тестами, интегрируем тесты в CI, проектируем деплой как часть архитектуры (контейнеры, env, миграции, миграционный/rollback-стратегии), поддерживаем безопасный поэтапный раскат, документируем API через OpenAPI/Swagger, держим спецификации рядом с кодом, версионируем публичные API и не ломаем их молча, и на всех уровнях Angular backend (Nest/Node/любая платформа) поддерживаем строгие слои, явные контракты, слабую связность, тестируемость и способность эволюционировать без «тотальных переписок».
start not from Nest/Express and decorators but from the service domain and API responsibilities, define business scenarios and the resources the backend actually needs to expose, split into layers: domain (models and rules), application/use-cases (commands/queries, orchestration), infrastructure (DB, queues, external APIs), web/transport (HTTP, WebSocket, GraphQL), treat Nest/Express as a transport and DI framework, keep HTTP details and decorators (@Req, @Res, @Body) out of the domain, map requests/responses to DTOs at the controller boundary, keep controllers as thin as possible: validate/normalize input, call use-case, map result to transport DTO, never put business logic directly into controllers and middleware, build architecture around use-case services/handlers (CreateUser, UpdateOrderStatus, GetUserProfile) rather than “universal” services, give each service and module a single clear responsibility, avoid god-services like AppService that know everything, clearly separate domain entities from persistence/ORM entities, do not push ORM entities directly into JSON responses or domain rules, isolate DB access behind repositories/ports and use interfaces + adapters over ORM/clients (TypeORM, Prisma, Mongoose), treat repositories as infrastructure, not domain, use Nest DI for dependency injection but do not turn it into a service locator: inject dependencies via constructors, keep dependencies pointing from upper layers to abstractions, avoid static singletons, move configuration (connection strings, secrets, URLs, flags) to env and config modules, never commit secrets, implement validation at the edge (class-validator, pipes) and duplicate critical invariants in the domain, separate transport DTOs and domain models and don’t mix them, treat errors as part of the public contract: define error codes and types (business/validation/technical) with human-readable messages, avoid leaking stack traces, use exception filters and interceptors to unify error handling and logging, design security from day one: authentication (JWT/OAuth/session), authorization (guards, policies, roles and permissions in the domain), protect against common attacks (SQL/NoSQL injection, reflected XSS, CSRF, brute force), keep minimal privileges for service accounts, log key events and errors with structured logs (interceptors, middleware), add a correlation id to each request, do not log passwords, tokens, or sensitive data, add metrics (latency, RPS, error types) and health-check endpoints, use pipes/guards/interceptors for cross-cutting concerns (validation, auth, rate limiting, caching) instead of duplicating logic across controllers, separate reads and writes (CQRS) where it helps, with dedicated query handlers and simple read models, strict domain models for writes, isolate external API calls behind client services/adapters with timeouts, retries and circuit breakers, make operations idempotent where retries are possible, structure Nest modules to reflect architecture and bounded contexts (UserModule, OrdersModule, BillingModule) rather than “misc”, avoid cyclic dependencies between modules, use shared modules only for truly shared cross-cutting concerns (logger, config, auth) and not as a dumping ground, write unit tests for domain services and use-cases without Nest, cover controllers and integration with DB/external services with integration and contract tests, wire tests into CI, design deployment as part of the architecture (containers, env, migrations, rollback strategies), support safe progressive rollout, document APIs with OpenAPI/Swagger, keep specs close to code, version public APIs and never break them silently, and at all levels of the Angular backend (Nest/Node/whatever) maintain clear layers, explicit contracts, loose coupling, high testability and the ability to evolve without constant rewrites.
#### Angular frontend
начинаем не с компонентов, модулей и Material, а с домена фронтенда и пользовательских сценариев, определяем ключевые флоу и состояния, которые должен поддерживать интерфейс, разделяем слои: domain (модели и бизнес-правила), application/state (use-cases, сервисы, state-management), infrastructure (API-клиенты, storage, адаптеры), presentation (Angular компоненты, модули, шаблоны), считаем backend просто внешним API, на границе слоя данных маппим транспортные DTO в доменные модели, не тянем сырые ответы HTTP прямо в компоненты, строим архитектуру вокруг feature-модулей и «фичей», а не вокруг «shared/core», даём каждому feature-модулю одну чёткую область (users, orders, admin), избегаем огромных AppModule и одинокого «features» без структуры, держим умные контейнерные компоненты (страницы) тонкими: они запрашивают данные через сервисы/use-cases, подписываются на state, пробрасывают данные вниз, презентационные компоненты делают только отображение, не знают про HTTP, Router и глобальное состояние, используем Angular DI и сервисы как слой application/infrastructure, но не превращаем каждый сервис в god-сервис, разделяем ответственность: сервисы данных, сервисы сценариев/use-cases, UI-сервисы, явно разделяем server state (данные с backend) и UI state (фильтры, выбранные элементы, открытые модалки), не складываем всё в один глобальный store, выбираем подход к стейту (signals, RxJS services, NgRx/Akita/т.п.) осознанно, не смешиваем несколько стейт-менеджеров без причины, держим эффекты (HTTP, localStorage, навигация) в application-слое (effects/services), а не внутри компонентов, оборачиваем API-вызовы в отдельный data-layer (ApiService/Repositories) с типами, валидацией, обработкой ошибок, не размазываем HttpClient по компонентам, проектируем интерфейсы доменных моделей и DTO, используем строгую типизацию (TypeScript), следим, чтобы UI не зависел от конкретной формы backend DTO, выносим маршрутизацию в RouterModule конфигурации по feature-модулям, описываем роуты как карту экранов и доменных разделов, не вшиваем логику навигации глубоко в компоненты, используем Guards/Resolvers для auth, загрузки критичных данных и защиты роутов, выносим дизайн-систему и общие компоненты в отдельные модули (UI/Shared), не дублируем стили, проектируем тему через переменные/токены и централизованные стили, закладываем адаптивность и доступность (семантическая разметка, aria-атрибуты, фокус), не хардкодим тексты в шаблонах, используем i18n/ngx-translate/аналог, держим тексты и переводы в ресурсах, учитываем локали и форматы дат/чисел, конфигурацию фронтенда (base API URL, feature flags, режимы) выносим в environment-файлы и конфиг-сервисы, не шьём URL в код, обращение к storage (localStorage/IndexedDB) инкапсулируем в сервисах, а не дергаем напрямую из вьюх, рассматриваем кэш и offline-поведение как часть архитектуры, явно обрабатываем состояния загрузки/ошибок/пустых данных в UI, не рассчитываем, что «всё всегда работает», проектируем error-boundary-like поведение (глобальный перехват ошибок, user-friendly сообщения), логируем важные действия и ошибки (например, через interceptor и сервис логирования), не тащим логирование в каждый компонент вручную, следим за производительностью: используем OnPush change detection, trackBy в *ngFor, lazy-loading модулей, code-splitting по route, не оптимизируем преждевременно без профилирования, строим структуру каталогов по архитектуре и фичам (domain, app/state, infra, ui/features), избегаем папок «utils/helpers» без предметного контекста, покрываем критичную бизнес-логику и state-менеджмент unit-тестами (Jest/Karma), компоненты тестируем для ключевых сценариев, имеем набор e2e-тестов (Cypress/Playwright) для главных пользовательских флоу, следим, чтобы фронтовая логика была по возможности фреймворк-агностичной (доменные сервисы и модели можно переносить), держим связку frontend↔backend через явные контракты (OpenAPI схемы, общие типы), обновляем фронт по изменению схем, а не по догадкам, и во всём Angular-фронтенде поддерживаем строгие границы слоёв, явные контракты, слабую связность между фичами, предсказуемое управление состоянием и высокую тестируемость.
start not from components, modules and Material but from the frontend domain and user scenarios, define key flows and states the UI must support, split into layers: domain (models and business rules), application/state (use-cases, services, state management), infrastructure (API clients, storage, adapters), presentation (Angular components, modules, templates), treat the backend as just an external API and map transport DTOs to frontend domain models at the data boundary, do not feed raw HTTP responses into components, structure the app around feature modules and features rather than just “shared/core”, give each feature module a clear area (users, orders, admin), avoid massive AppModule and a lonely “features” folder without structure, keep smart container components (pages) thin: they request data via services/use-cases, subscribe to state and pass props down, presentation components only render UI and know nothing about HTTP, Router or global state, use Angular DI and services as your application/infrastructure layer but avoid turning every service into a god-service, separate responsibilities: data services, use-case/services, UI services, clearly separate server state (backend data) from UI state (filters, selected items, modal open flags), do not dump everything into one global store, choose a state approach (signals, RxJS services, NgRx/Akita/etc.) consciously, don’t mix multiple state managers without a strong reason, keep side effects (HTTP, localStorage, navigation) in the application layer (effects/services), not inside components, wrap API calls in a dedicated data layer (ApiService/repositories) with typing, validation, and error handling, avoid scattering HttpClient usage in components, design interfaces for domain models and DTOs with strict TypeScript typing so the UI doesn’t depend on backend DTO shape directly, configure routing via RouterModule per feature module, describe routes as a map of screens and domain sections, avoid deeply embedding navigation logic in components, use Guards/Resolvers for auth, loading critical data and protecting routes, put the design system and shared UI components in dedicated modules (UI/Shared) instead of duplicating styles, design theming via tokens/variables and centralized styles, build responsiveness and accessibility in from the start (semantic markup, aria attributes, focus management), avoid hardcoding text in templates, use i18n/ngx-translate/alternatives, keep texts and translations in resource files, respect locale-specific formats for dates/numbers, put frontend configuration (base API URL, feature flags, modes) into environment files and config services, not into hardcoded strings, encapsulate storage access (localStorage/IndexedDB) in services rather than calling it directly from views, treat caching and offline behavior as architectural concerns, explicitly handle loading/error/empty states in the UI instead of assuming “everything is fast and successful”, design error-boundary-like behavior (global error catching, user-friendly messages), log important actions and errors via interceptors and logging services, not directly in every component, watch performance: use OnPush change detection, trackBy for *ngFor, lazy-load modules, do route-based code splitting, but avoid premature optimization without profiling, structure folders by architecture and features (domain, app/state, infra, ui/features), avoid “utils/helpers” dumping grounds, cover critical business logic and state management with unit tests, test components for key behaviors, keep a set of e2e tests (Cypress/Playwright) for main user flows, try to keep core frontend logic framework-agnostic where possible (domain services/models could be reused elsewhere), keep the frontend↔backend link via explicit contracts (OpenAPI schemas, shared types), evolve the frontend based on schema changes rather than guesswork, and across the Angular frontend maintain strict layer boundaries, explicit contracts, loose coupling between features, predictable state management, and high testability.
### Java Backend
начинаем не с Spring Boot, аннотаций и JPA-entity, а с домена системы и бизнес-требований, формулируем ключевые сценарии и API, которые реально нужны, разделяем слои: domain (модели и инварианты), application/use-cases (команды/queries, оркестрация), infrastructure (БД, брокеры, внешние API), web/transport (REST, gRPC, GraphQL), рассматриваем Spring/Jakarta/Micronaut/Quarkus как инфраструктуру и DI-фреймворк, а не как домен, не тащим детали HTTP (Request, Response, HttpServletRequest) и аннотации фреймворка в доменные модели, маппим запрос/ответ в DTO на границе контроллера, держим контроллеры максимально тонкими: парсим и валидируем вход, вызываем use-case, маппим результат в ответ, не пишем бизнес-логику внутри контроллеров и фильтров, проектируем application-слой в виде сервисов/хендлеров use-cases (CreateOrderService, CancelSubscriptionHandler и т.п.), каждый use-case имеет одну чёткую ответственность, избегаем god-сервисов типа `UserService` который делает всё, явно разделяем domain-model и persistence-model: JPA entities — это инфраструктура, доменные объекты — отдельные классы с инвариантами и поведением, не гоняем JPA entities по всем слоям и не сериализуем их напрямую в JSON, изолируем работу с БД в репозиториях/адаптерах, используем интерфейсы (порт) + реализацию (адаптер над JPA/JDBC/MyBatis), домен зависит от интерфейсов, а не от конкретного ORM, внедряем зависимости через конструктор (Spring DI), не используем статические синглтоны и сервис-локаторы, не прячем зависимости за `@Autowired` полями и вызовами `ApplicationContext.getBean`, выносим конфигурацию (URL-ы, строки подключения, ключи, флаги) в `application.yml`/env/secret-store, никогда не хардкодим чувствительные данные в коде, используем профили (dev/test/stage/prod) и отдельные конфиги на окружения, валидируем входные данные на web-границе (Bean Validation, @Valid), а домен дополнительно защищаем своими проверками инвариантов, разделяем DTO (request/response) и доменные объекты, не смешиваем, проектируем ошибки как часть публичного контракта: единый формат error-response (код, тип, message, детали), бизнес-ошибки отличаем от технических (ошибка БД, таймаут), не вываливаем stacktrace и внутренние детали наружу, используем `@ControllerAdvice`, `ExceptionHandler` и фильтры для унификации обработки ошибок и логирования, думаем о безопасности с нуля: аутентификация (Spring Security, JWT/OAuth2), авторизация (ролевая модель, permissions в домене), validate и sanitize всех входных данных, защита от типовых атак (SQL/NoSQL injection, XSS через отражение, CSRF, SSRF), минимальные права у технических аккаунтов, чёткая модель того, как пользователь/клиент идентифицируется и как этот контекст прокидывается в домен, проектируем обработку внешних API через клиентские сервисы (RestTemplate/WebClient/HTTP-клиент) как адаптеры, добавляем таймауты, ретраи с backoff, circuit breaker (resilience4j/аналог), делаем операции идемпотентными там, где возможны повторы, явно учитываем eventual consistency в потоках с несколькими сервисами, разделяем операции чтения и записи (CQRS), когда это даёт выигрыш: отдельные query-сервис(ы) и лёгкие проекции для чтения, строгие доменные модели и транзакции для записи, работу с транзакциями проектируем осознанно: границы транзакций в application-слое, не размазываем `@Transactional` по всем уровням, понимаем последствия lazy-loading и N+1, логируем важные события в структурированном виде (SLF4J + логгер), не логируем секреты, добавляем корреляционный id, метрики (latency, RPS, ошибки по типам), health-check endpoints (actuator/аналог), строим наблюдаемость (логи, метрики, tracing), структуру модулей/пакетов строим по архитектуре: `domain`, `application`, `infrastructure`, `web`, а не по «controllers/services/utils», избегаем классов `*Manager`, `*Helper` без контекста, пишем юнит-тесты для доменных сервисов и use-cases без Spring-контейнера, интеграционные тесты используем для контроллеров/репозиториев/интеграций (Testcontainers, MockMvc, WebTestClient), встраиваем тесты в CI, проектируем сборку/деплой (Maven/Gradle + контейнеры/оркестратор) как часть архитектуры, закладываем стратегию миграций БД (Flyway/Liquibase) и отката, документируем публичные API (OpenAPI/Swagger), версионируем контракты, не ломаем их молча, и на всех уровнях Java backend держим строгие границы слоёв, явные контракты, слабую связность, тестируемость и возможность эволюции без тотальных переписок.
start not from Spring Boot, annotations and JPA entities but from the system domain and business requirements, define key scenarios and the APIs you actually need, split into layers: domain (models and invariants), application/use-cases (commands/queries, orchestration), infrastructure (DB, brokers, external APIs), web/transport (REST, gRPC, GraphQL), treat Spring/Jakarta/Micronaut/Quarkus as infrastructure and DI framework, not as your domain, keep HTTP details (Request, Response, HttpServletRequest) and framework annotations out of domain models, map requests/responses to DTOs at the controller boundary, keep controllers as thin as possible: parse and validate input, call a use-case, map the result to a response, never put business logic into controllers and filters, design the application layer as services/handlers for use-cases (CreateOrderService, CancelSubscriptionHandler, etc.), each with a single responsibility, avoid god-services like `UserService` that do everything, clearly separate domain models from persistence models: JPA entities are infrastructure, domain objects are separate classes with invariants and behavior, don’t pass JPA entities across all layers or serialize them directly to JSON, isolate DB access in repositories/adapters, use interfaces (ports) plus implementations (adapters over JPA/JDBC/MyBatis), let the domain depend on interfaces, not concrete ORMs, inject dependencies via constructors (Spring DI), avoid static singletons and service locators, don’t hide dependencies behind `@Autowired` fields or `ApplicationContext.getBean`, move configuration (URLs, connection strings, keys, flags) to `application.yml`/env/secret store, never hardcode sensitive data, use profiles (dev/test/stage/prod) and env-specific configs, validate incoming data at the web edge (Bean Validation, @Valid) and also enforce invariants in the domain, separate DTOs (request/response) and domain objects, treat errors as part of the public contract: a unified error response format (code, type, message, details), distinguish business errors from technical ones (DB failure, timeout), avoid exposing stack traces and internal details, use `@ControllerAdvice`, `@ExceptionHandler` and filters to unify error handling and logging, design security from day one: authentication (Spring Security, JWT/OAuth2), authorization (role model, domain permissions), validate and sanitize all inputs, protect against common attacks (SQL/NoSQL injection, reflected XSS, CSRF, SSRF), minimize privileges for service accounts, have a clear identity model for users/clients and propagate this context into the domain, wrap external API calls in client services (RestTemplate/WebClient/HTTP client) as adapters, add timeouts, retries with backoff, and circuit breakers (resilience4j/etc.), make operations idempotent where retries are possible, explicitly model eventual consistency in multi-service flows, separate reads and writes (CQRS) when useful: dedicated query services and light projections for reads, strict domain models and transactions for writes, design transaction boundaries consciously in the application layer, don’t spray `@Transactional` everywhere, understand lazy loading and N+1 implications, log important events in a structured way (SLF4J + logger), don’t log secrets, add correlation IDs, expose metrics (latency, RPS, error types) and health-check endpoints (actuator/etc.), build observability (logs, metrics, tracing), structure modules/packages by architecture: `domain`, `application`, `infrastructure`, `web` rather than “controllers/services/utils”, avoid `*Manager` / `*Helper` classes without clear context, write unit tests for domain services and use-cases without the Spring container, use integration tests for controllers/repositories/integrations (Testcontainers, MockMvc, WebTestClient), wire tests into CI, design build/deploy (Maven/Gradle + containers/orchestrator) as part of the architecture, plan DB migration/rollback strategy (Flyway/Liquibase), document public APIs (OpenAPI/Swagger), version your contracts and never break them silently, and at every level of the Java backend keep strict layer boundaries, explicit contracts, loose coupling, testability, and evolutionary maintainability.
### Java Frontend (JavaFX / Vaadin / desktop/web UI на Java)
начинаем не с JavaFX/Vaadin-компонентов, FXML и `@Route`, а с домена клиентского приложения и пользовательских сценариев, формулируем, какие задачи решает UI и какие сущности домена в нём живут, разделяем слои: domain (модели и правила), application/use-cases (сервисы, состояние, координация), infrastructure (API-клиенты, локальное хранилище, файловая система), presentation (View/Controller/ViewModel/Presenter, компоненты UI), рассматриваем JavaFX/Vaadin как презентационный слой и адаптер, не тянем их типы (`Node`, `Button`, `TextField`, Vaadin `Component`) в доменную модель, строим архитектуру вокруг паттернов MVVM/MVP/MVC: View отвечает за отображение, ViewModel/Presenter — за состояние и действия, Model/Domain — за бизнес-правила, даём каждому экрану/фиче свой View + ViewModel/Presenter, не делаем один гигантский `MainController`/`MainView`, держим контроллеры/презентеры максимально тонкими: связывают пользовательские действия с use-case сервисами и обновлением состояния, не пишем бизнес-логику в обработчиках кликов и слушателях напрямую, выносим всю работу с REST/gRPC/БД в сервисы/application-слой, UI вызывает абстракции (интерфейсы) а не конкретные HTTP-клиенты или JDBC, маппим DTO backend’а в доменные модели на границе data-слоя, не таскаем сырые JSON/DTO по компонентам, отделяем domain-model от view-model: view-модели заточены под отображение и биндинги (observable properties), доменные модели — про инварианты и бизнес-логику, явно разделяем UI state (выбранная вкладка, раскрытые секции, активный диалог) и server state (данные с backend’а), не делаем один глобальный статический объект, в который всё складируем, используем биндинги и observable-паттерны JavaFX (Property, ObservableList) или механизмы Vaadin для связи состояния и UI, избегаем ручного «протаскивания» значений в каждую кнопку/лейбл, выносим навигацию (смена сцен в JavaFX, роутинг в Vaadin) в отдельный сервис/слой, ViewModel/Presenter знают про абстракцию навигации, но не создают окна/страницы напрямую, конфигурацию UI (base API URL, флаги, режимы) выносим в конфиг/окружение, не хардкодим URL и ключи в коде, доступ к локальному хранилищу/файлам (Preferences, файлы, кэш) инкапсулируем в сервисах, а не вызываем прямо из UI-слоя, проектируем обработку состояний загрузки, ошибок и пустых данных как часть UX: индикаторы, сообщения, дисабл кнопок, не рассчитываем, что backend всегда быстрый и безошибочный, логируем ключевые действия пользователя и ошибки в инфраструктурном слое, не показываем пользователю stacktrace, а выводим понятные сообщения, думаем о многопоточности: все долгие операции (сетевые вызовы, тяжёлые вычисления) выносим из UI-потока (JavaFX Application Thread / Vaadin UI thread), используем `Task`, `Service`, `CompletableFuture`, очереди, чтобы не замораживать интерфейс, внимательно управляем ресурсами и подписками (listeners, event handlers), освобождаем их при закрытии экранов чтобы избежать утечек памяти, строим структуру пакетов по архитектуре и фичам: `domain`, `app`/`usecase`, `infra`, `ui`/`view`/`presentation`, а внутри — модули по предметным областям (users, orders и т.п.), избегаем папок `utils/helpers` без контекста, выносим дизайн-систему (общие контролы, стили, темы) в отдельный слой: CSS/Theme для JavaFX, общий набор компонентов для Vaadin, не копипастим стили между экранами, закладываем i18n: все тексты в ресурсах/бандлах, а не вшиты в код, учитываем локали, форматы дат/чисел, тестируем бизнес-логику и application-слой отдельно от UI (юнит-тесты), минимизируем логику, которая жестко привязана к конкретному фреймворку, UI-слой тестируем через интеграционные/GUI-тесты там, где это оправдано, обеспечиваем, чтобы доменные сервисы было легко реиспользовать в других оболочках (консоль, web, другой UI), держим Java frontend тонким слоем над хорошо спроектированным приложением и доменом, поддерживаем структурированные архитектурные решения (ADR, схемы модулей), и на всех уровнях Java UI кода строго разделяем домен и презентацию, держим явные контракты между слоями, слабую связность и высокую тестируемость.
start not from JavaFX/Vaadin widgets, FXML and `@Route`, but from the client-side domain and user scenarios, define what problems the UI solves and which domain concepts it manipulates, split into layers: domain (models and rules), application/use-cases (services, state, coordination), infrastructure (API clients, local storage, file system), presentation (View/Controller/ViewModel/Presenter, UI components), treat JavaFX/Vaadin as the presentation adapter, keep their types (`Node`, `Button`, `TextField`, Vaadin `Component`) out of the domain model, design architecture around MVVM/MVP/MVC: View handles rendering, ViewModel/Presenter handles state and actions, Model/Domain handles business rules, give each screen/feature its own View + ViewModel/Presenter, don’t create a giant `MainController`/`MainView`, keep controllers/presenters thin: they link user actions to use-case services and state updates, don’t bury business logic in click handlers and listeners, move all REST/gRPC/DB work into services/application layer, let the UI call abstractions (interfaces) instead of concrete HTTP clients or JDBC, map backend DTOs to domain models at the data layer boundary rather than pushing raw JSON/DTOs into components, separate domain models from view models: view models are shaped for presentation and bindings (observable properties), domain models for invariants and business logic, clearly separate UI state (selected tab, expanded sections, active dialog) from server state (backend data), do not use a giant static global object to hold everything, leverage bindings and observable patterns in JavaFX (Property, ObservableList) or Vaadin mechanisms to keep state and UI in sync, avoid manually pushing every value into each label/button, encapsulate navigation (scene switching in JavaFX, routing in Vaadin) into a dedicated service/layer, let ViewModel/Presenter depend on navigation abstraction instead of creating windows/pages directly, move UI configuration (base API URL, flags, modes) to config/environment instead of hardcoding URLs and keys, encapsulate access to local storage/files (Preferences, files, caches) in services, not directly from UI layer, design loading/error/empty states as part of UX: progress indicators, messages, disabled buttons, don’t assume the backend is always fast and error-free, log key user actions and errors in the infrastructure layer, never show stack traces to users, but display friendly messages, consider concurrency: all long-running operations (network calls, heavy computations) must leave the UI thread (JavaFX Application Thread / Vaadin UI thread), use `Task`, `Service`, `CompletableFuture`, queues, etc. to keep the UI responsive, carefully manage resources and subscriptions (listeners, event handlers), release them when screens are closed to avoid memory leaks, structure packages by architecture and feature: `domain`, `app`/`usecase`, `infra`, `ui`/`view`/`presentation`, and inside that by domain modules (users, orders, etc.), avoid dumping grounds like `utils/helpers` without context, move the design system (shared controls, styles, themes) into a dedicated layer: CSS/themes for JavaFX, shared component sets for Vaadin, don’t copy-paste styles across screens, build i18n in from the start: keep texts in resource bundles instead of hardcoded strings, respect locales and date/number formats, test business logic and application layer separately from the UI (unit tests), minimize logic tightly tied to the specific framework, test the UI layer via integration/GUI tests where it makes sense, make sure domain services can be reused from other shells (console, web, another UI), keep the Java frontend as a thin layer over a well-designed application and domain, document architectural decisions (ADRs, module diagrams), and throughout the Java UI code enforce strict separation between domain and presentation, explicit contracts between layers, loose coupling, and high testability.
### Go backend
начинаем не с `net/http`, роутеров и фреймворков, а с домена сервиса и бизнес-требований, формулируем ключевые сценарии и API, которые реально нужны, явно описываем ресурсы и операции, выделяем bounded contexts, строим архитектуру от домена и use-cases, а не от конкретной web-библиотеки, разделяем слои: domain (чистые модели и инварианты), application/use-cases (команды/queries, оркестрация), infrastructure (БД, очереди, внешние API, файлы), transport/web (HTTP, gRPC, GraphQL), считаем HTTP просто транспортом на границе системы, не тянем `*http.Request`, `http.ResponseWriter` и детали роутера в домен, маппим запросы/ответы в свои DTO/command/query на границе handler’ов, держим HTTP-обработчики максимально тонкими: парсим вход, валидируем, вызываем use-case, сериализуем результат, не пишем бизнес-логику внутри handler’ов и middleware, программируем против абстракций: домен и application зависят от интерфейсов репозиториев, клиентов внешних сервисов, брокеров сообщений, а инфраструктура реализует эти интерфейсы на Go-уровне, используем интерфейсы и композицию вместо наследования, избегаем god-пакетов и структур типа `Service` которая знает обо всём, даём каждому пакету и типу одну чёткую ответственность, не размещаем всё в `package main` и паре «helpers», проектируем зависимости направленными: внутренние слои не знают о внешних, domain не импортирует `net/http`, `database/sql` и конкретные драйверы, выносим SQL/ORM, Redis, Kafka и прочие детали в отдельные пакеты adapters/infra, внедряем зависимости явно через конструкторы (constructor injection), не используем глобальные синглтоны и скрытое состояние, минимизируем использование `init()` и глобальных переменных, конфигурацию (DSN, URL, таймауты, флаги) читаем из окружения/конфиг-файлов, описываем её отдельной структурой, не хардкодим секреты в коде, помечаем чувствительные данные и храним их в секрет-хранилищах, проектируем модель ошибок осознанно: ошибки — часть публичного контракта функций, не боимся оборачивать ошибки с контекстом (`fmt.Errorf("...: %w", err)`), различаем бизнес-ошибки и технические, не глушим `err` без логирования и реакции, не сливаем все ошибки в `error` без типов и контекста, продумываем использование `context.Context`: передаём его по всему пути запроса, не сохраняем в struct и не используем после отмены, используем context для таймаутов, отмены и id запроса, строим работу с БД через интерфейсы репозиториев и `*sql.DB`/`*sql.Tx`/ORM только в инфраструктурном слое, описываем транзакционные границы на уровне use-case, а не раскидываем `Begin/Commit/Rollback` по всему коду, разделяем операции чтения и записи там, где это даёт плюс (CQRS-стиль), отдельные query-сервисы и лёгкие DTO для чтения, строгие доменные модели для записи, проектируем конкуррентность как часть архитектуры: чётко определяем, что можно делать параллельно, что должно быть защищено, используем горутины и каналы осознанно, избегаем шаренного изменяемого состояния без защиты, используем примитивы sync только в инфраструктурных местах, делаем API функций явным в плане потокобезопасности, выносим все сетевые вызовы и IO в адаптеры, добавляем туда таймауты, ретраи, backoff, circuit breaker, думаем об идемпотентности при ретраях, логируем ключевые события и ошибки структурированно (логгер, поля, уровни), не логируем пароли, токены, персональные данные, добавляем correlation id, учитываем уровень логирования по окружениям, добавляем метрики (RPS, latency, коды ответов, ошибки), health-check endpoints, интегрируем tracing, строим структуру модулей/пакетов по архитектуре и домену: `internal/domain/...`, `internal/app/...`, `internal/infra/...`, `internal/transport/http/...`, а не по типу файла (`models`, `utils`), избегаем `util`, `common` без контекста, проектируем контракты пакетов так, чтобы большинство типов оставались невидимыми снаружи (unexported), экспортируем только то, что реально публичный API, пишем модульные тесты на доменные сервисы и use-cases без HTTP и БД (табличные тесты, сравнение инвариантов), используем интеграционные тесты для репозиториев и handler’ов (Testcontainers, in-memory, http-тесты), не тащим фреймворк в юнит-тесты, подключаем тесты в CI, продумываем сборку и деплой (multi-stage Docker, `go build`, теги, версии) как часть архитектуры, используем `internal` для encapsulation, документируем публичные функции и пакеты комментариями, фиксируем архитектурные решения короткими ADR, закладываем возможность эволюции: новые адаптеры и transports добавляются без ломки домена, старые контрактные формы можно поддерживать параллельно, и на всех уровнях Go backend держим строгие границы слоёв, явные интерфейсы, явную передачу зависимостей и контекста, слабую связность и тестируемость.
start not from `net/http`, routers and frameworks but from the service domain and business requirements, define the key scenarios and APIs you actually need, explicitly describe resources and operations and identify bounded contexts, design the architecture from the domain and use cases, not from a specific web library, split into layers: domain (pure models and invariants), application/use-cases (commands/queries, orchestration), infrastructure (DB, queues, external APIs, files), transport/web (HTTP, gRPC, GraphQL), treat HTTP as just a transport at the system boundary, keep `*http.Request`, `http.ResponseWriter` and router details out of the domain, map requests/responses to your own DTOs/commands/queries at handler boundaries, keep HTTP handlers as thin as possible: parse input, validate, call a use-case, serialize output, never put business logic into handlers and middleware, code against abstractions: domain and application depend on interfaces for repositories, external service clients, message brokers, and infrastructure implements these interfaces in concrete Go packages, use interfaces and composition instead of inheritance, avoid god-packages and giant `Service` structs that know everything, give each package and type a single clear responsibility, don’t dump everything into `package main` plus a couple of “helpers”, make dependencies directional: inner layers don’t know about outer ones, the domain does not import `net/http`, `database/sql` or specific drivers, move SQL/ORM, Redis, Kafka and other details into separate adapters/infra packages, inject dependencies explicitly via constructors, avoid global singletons and hidden state, minimize `init()` and global variables, read configuration (DSNs, URLs, timeouts, flags) from environment/config files into explicit config structs, never hardcode secrets in code, mark sensitive data and keep it in secret stores, design error handling consciously: errors are part of the public contract of functions, wrap errors with context (`fmt.Errorf("...: %w", err)`), distinguish business from technical errors, never ignore `err` without logging and handling, don’t collapse everything into a bare `error` without types/context, think carefully about `context.Context`: thread it through the entire call path, don’t store it in structs or use after cancel, rely on context for timeouts, cancellation and request IDs, implement DB access through repository interfaces and `*sql.DB`/`*sql.Tx`/ORM only in infrastructure, define transaction boundaries at the use-case layer instead of sprinkling `Begin/Commit/Rollback` everywhere, separate reads and writes when it helps (CQRS-style): dedicated query services and light DTOs for reads, strict domain models for writes, treat concurrency as an architectural concern: clearly decide what can run in parallel and what must be protected, use goroutines and channels deliberately, avoid shared mutable state without protection, use sync primitives only where really needed and document thread-safety, move all network calls and IO into adapters with timeouts, retries, backoff and circuit breakers, design operations to be idempotent under retries, log key events and errors in a structured way (logger with fields and levels), avoid logging passwords, tokens or personal data, add correlation IDs, adjust log levels per environment, add metrics (RPS, latency, response codes, error types), health-check endpoints and tracing, structure modules/packages by architecture and domain: `internal/domain/...`, `internal/app/...`, `internal/infra/...`, `internal/transport/http/...` instead of file type (`models`, `utils`), avoid `util`/`common` dumping grounds, design package contracts so that most types remain unexported, export only what is truly public API, write unit tests for domain services and use-cases without HTTP and DB (table-driven tests, invariants), use integration tests for repositories and handlers (Testcontainers, in-memory DB, HTTP tests), don’t drag frameworks into unit tests, wire tests into CI, design build and deploy (multi-stage Docker, `go build`, tags, versions) as part of the architecture, use `internal` to enforce encapsulation, document public functions and packages with comments, record architectural decisions with short ADRs, plan for evolution: new adapters and transports can be added without breaking the domain, legacy contract shapes can be supported side-by-side, and at every level of the Go backend keep strict layer boundaries, explicit interfaces, explicit dependency and context passing, loose coupling and high testability.
### Go frontend (server-driven UI, templates, WASM)
начинаем не с `html/template`, WASM и htmx, а с домена фронтенда и пользовательских сценариев, формулируем, какие задачи решает интерфейс и какие флоу в нём есть, отделяем то, что должно жить на клиенте, от того, что можно рендерить на сервере, строим архитектуру от use-cases и состояния, а не от конкретного шаблонизатора или JS-фреймворка, разделяем слои: domain (модели и правила, общие с backend там где это оправдано), application/state (use-cases, состояние, координация запросов к API/бэкенду), infrastructure (HTTP-клиенты для внешних API, кеш, storage), presentation (шаблоны, компоненты, WASM-UI/виджеты), если делаем server-driven UI на Go (HTML-шаблоны, htmx, server-side rendering), относимся к веб-слою как к адаптеру, не тянем `http.Request` и детали router’а внутрь шаблонов и домена, строим обработчики как тонкие контроллеры: определяют use-case, собирают данные из application-слоя, выбирают шаблон, рендерят ответ, не вставляем бизнес-логику в template-файлы, держим их максимально декларативными, не пишем сложные условия и циклы, которые скрывают логику, маппим доменные модели в view-модели/DTO для шаблонов, не передаём в шаблон сырые сущности БД и огромные структуры, разделяем UI-state (выбранный таб, пагинация, модалки) и серверное состояние (данные домена), не превращаем сессию в свалку всего подряд, конфигурацию фронтенда (базовый URL API, флаги, режимы) выносим в конфиг/окружение и, если нужно, явно прокидываем в шаблоны, не хардкодим адреса и ключи в шаблонных файлах, оборачиваем работу с cookie/session в отдельные сервисы/абстракции, а не используем `http.SetCookie` хаотично в каждом handler’е, проектируем обработку ошибок и валидации как часть UX: при ошибке валидации возвращаем ту же форму с сообщениями, не теряем введённые данные, не вываливаем пользователю stacktrace и внутренние детали, логируем ошибки на backend’е, показываем понятные сообщения, следим за безопасностью вывода: экранируем HTML, не даём пользователю инъектировать скрипты через шаблоны, рассматриваем CSP, безопасную работу с формами, CSRF-защиту, если используем Go+WASM для rich UI, всё равно разделяем домен и UI: доменные модели и use-cases в отдельных пакетах, WASM-код — тонкий слой взаимодействия с DOM и событиями, не смешиваем бизнес-логику и прямые DOM-манипуляции, строим архитектуру WASM-части вокруг явного состояния и сообщений (Redux-подобный подход, MVU, компонентная модель), а не разрозненных callback’ов, явно разделяем server state (данные с backend) и локальный UI state, используем HTTP/WS-клиенты как абстракции, а не раскидываем `fetch`/XHR-вызовы по всему коду, при необходимости синхронизации с backend проектируем протокол взаимодействия (REST/JSON, WebSocket-сообщения) как стабильный контракт, оборачиваем его в клиенты, используем строгую типизацию и структуры/генерики Go для моделей сообщения, продумываем производительность: не делаем шаблоны/рендеринг сверхсложными, используем кеширование там, где это оправдано, отдаём статику (CSS, JS) эффективно, используем gzip/br, HTTP/2/3, но не оптимизируем преждевременно без измерений, строим структуру пакетов/директорий по архитектуре и фичам: `internal/ui/templates/...`, `internal/ui/handlers/...`, `internal/ui/wasm/...`, `internal/app/...`, `internal/domain/...`, избегаем папок `views`/`utils` без структуры, выносим общий layout/partials (header, footer, навигация, компоненты) в переиспользуемые шаблоны, не копипастим HTML везде, закладываем i18n: строки интерфейса выносим в ресурсы, шаблоны используют функции/помощники для выбора языка, учитываем локали и форматирование дат/чисел, пишем тесты на application-логику и формирование view-моделей отдельно от шаблонизатора, проверяем рендеринг критичных шаблонов через golden-тесты или snapshot-подход, следим за доступностью (a11y): семантическая разметка, aria-атрибуты, фокус, клавиатурная навигация, если гоняем Go-код в браузер (WASM), деградируем graceful при его отсутствии, имеем базовый server-rendered fallback, проектируем фронт на Go так, чтобы его доменные и application-слои были переиспользуемы и с другими UI (JS-SPA, mobile, desktop), а сам Go-frontend был тонким адаптером, и везде поддерживаем строгие границы: домен отдельно, состояние отдельно, представление отдельно, явные контракты, слабую связность и тестируемость.
start not from `html/template`, WASM and htmx but from the frontend domain and user scenarios, define what problems the UI solves and which flows it must support, separate what must live on the client from what can be rendered on the server, design from use cases and state, not from a specific templating engine or JS framework, split into layers: domain (models and rules, possibly shared with the backend where it makes sense), application/state (use-cases, state, coordination of calls to APIs/backends), infrastructure (HTTP clients for external APIs, cache, storage), presentation (templates, components, WASM UI/widgets), if you build a server-driven UI in Go (HTML templates, htmx, server-side rendering), treat the web layer as an adapter, keep `http.Request` and router details out of templates and domain, build handlers as thin controllers: they pick a use-case, gather data from the application layer, choose a template and render a response, never stuff business logic into template files, keep them as declarative as possible, avoid complex logic in templates that hides behavior, map domain models into view models/DTOs for templates, don’t pass raw DB entities and giant structs straight to the view, separate UI state (selected tab, pagination, active modals) from server state (domain data), don’t turn the session into a dumping ground for everything, move frontend configuration (base API URLs, flags, modes) into config/env and, if needed, pass them explicitly into templates, avoid hardcoding addresses and keys in templates, encapsulate cookie/session handling in dedicated services/abstractions instead of using `http.SetCookie` randomly in handlers, design error and validation handling as part of UX: on validation errors return the same form with messages and preserved inputs, don’t leak stack traces or internal details to users, log backend errors and show human-friendly messages, keep output safe: HTML-escape properly and prevent script injection through templates, consider CSP, safe forms and CSRF protection, if you use Go+WASM for a richer UI, still keep domain and UI separated: domain models and use-cases live in their own packages, WASM code is a thin layer around DOM/events, don’t mix business logic with direct DOM manipulation, structure the WASM side around explicit state and messages (Redux-like, MVU, component model) rather than random callbacks, clearly separate server state (backend data) from local UI state, wrap HTTP/WS calls in client abstractions instead of scattering `fetch`/XHR-equivalents everywhere, for backend communication define interaction protocols (REST/JSON, WebSocket messages) as stable contracts and wrap them in clients, use Go’s strong typing, structs and generics for message models, think about performance: keep templates/rendering simple enough, use caching where it helps, serve static assets (CSS, JS) efficiently, use gzip/br, HTTP/2/3, but don’t optimize prematurely without measurements, structure packages/directories by architecture and features: `internal/ui/templates/...`, `internal/ui/handlers/...`, `internal/ui/wasm/...`, `internal/app/...`, `internal/domain/...`, avoid “views”/“utils” dumping grounds, extract shared layout/partials (header, footer, nav, components) into reusable templates instead of copy-pasting HTML, build i18n in: move UI text into resource files, use helpers/functions in templates to pick language, respect locales and date/number formatting, test application logic and creation of view models separately from the templating engine, test rendering of critical templates with golden/snapshot tests, care about accessibility (a11y): semantic markup, aria attributes, focus, keyboard navigation, if you run Go code in the browser via WASM, provide graceful degradation when WASM is unavailable, with a basic server-rendered fallback, design the Go frontend so that its domain and application layers can be reused with other UIs (JS SPA, mobile, desktop) while the Go frontend itself stays a thin adapter, and everywhere maintain strict separation of domain, state and presentation, explicit contracts, loose coupling and high testability.
### SQL / реляционные БД
начинаем не с таблиц и индексов, а с домена и смысла данных, формулируем какие факты о мире мы храним, какие вопросы к данным будем задавать, какие инварианты обязаны соблюдаться, проектируем схему БД от доменной модели, а не от ORM и не от UI-формочек, стараемся, чтобы каждая таблица отражала один чёткий тип сущности или связи, а не была «ведром всего подряд», сначала нормализуем до разумного уровня (обычно до 3NF), чтобы убрать дубликаты и аномалии, а потом *осознанно* денормализуем там, где это даёт явную пользу по производительности и простоте, не используем БД как «просто ключ-значение стор» без схемы, если нам важна целостность данных, выбираем типы данных осмысленно: не складываем всё в `VARCHAR` и `TEXT`, используем числовые типы, даты/время, boolean, enum/ограничения, учитываем диапазоны и точность, минимизируем использование `NULL`, делаем поля not null по умолчанию, разрешаем `NULL` только когда это действительно осмысленное состояние, явно задаём первичные ключи для всех таблиц, используем surrogate keys (INT/BIGINT/UUID) или естественные ключи там, где это оправдано, не меняем первичные ключи постфактум без крайней необходимости, обязательно задаём внешние ключи и референциальную целостность на уровне БД, не полагаемся только на приложение для сохранения связей, используем `UNIQUE` и `CHECK`-ограничения для инвариантов и бизнес-правил, не кладём все правила только в код, моделируем связи явно: one-to-many через внешние ключи, many-to-many через join-таблицы, избегаем «полиморфных» связей «на всё подряд», где это ломает целостность и усложняет запросы, придерживаемся понятной и согласованной схемы именования таблиц, колонок, индексов, ключей, не плодим сокращений и магических трёхбуквенных названий без контекста, заранее продумываем модель мульти-арендности: отдельные БД/схемы на арендатора или общий schema + `tenant_id`/RLS, обеспечиваем изоляцию и удобство миграций, проектируем миграции схемы как часть архитектуры: используем миграции (Flyway/Liquibase/любые инструменты), версия схемы — в репозитории, не меняем таблицы в проде руками «через консоль», миграции делаем обратнос совместимыми, чтобы можно было катить код поэтапно (старый код работает с новой схемой и наоборот там, где возможно), для больших таблиц и критичных сервисов проектируем zero-downtime миграции: добавляем новые колонки/таблицы, заполняем их по частям, переключаемся, потом чистим старое, дизайн транзакций продумываем отдельно: понимаем уровни изоляции (read committed, repeatable read, serializable), выбираем дефолт осознанно, в application-слое явно задаём границы транзакций, не держим транзакции открытыми дольше, чем нужно, избегаем долгих SELECT внутри транзакций, которые блокируют других, явно разделяем бизнес-ошибки от конфликтов блокировок и deadlock, делаем ретраи там, где это оправдано, проектируем индексы по запросам, а не по ощущениям: сначала понимаем типичные запросы, затем делаем подходящие индексы, используем составные индексы и порядок колонок в них осмысленно, избегаем «индекса на каждую колонку», следим за размером и стоимостью обновлений, проверяем планы через `EXPLAIN`/`EXPLAIN ANALYZE`, не оптимизируем «на глаз», стараемся не писать бизнес-логику в хранимых процедурах и триггерах «просто потому что можно», используем хранимки/триггеры для инвариантов, аудита, тяжёлых пакетных операций там, где это действительно даёт выигрыш и контролируем сложность, в приложении всегда используем параметризованные запросы/подготовленные выражения, не собираем SQL через конкатенацию строк, защищаемся от SQL-инъекций по умолчанию, относимся к ORM как к помощнику, а не как к истине: знаем, какой SQL он генерирует, при необходимости пишем явные запросы, не позволяем ORM ломать нормальную модель данных ради удобства кода, проектируем слой доступа к данным (репозитории/DAO) как явный порт, не даём произвольным кускам приложения писать SQL как попало, думаем о согласованности и производительности: избегаем N+1-запросов, группируем операции, используем `JOIN`/batch-инсерты/апдейты там, где это разумно, учитываем, что база — общий узкий ресурс, не превращаем её в «чёрный ящик» под каждую маленькую операцию, заранее думаем о росте данных: планируем архивирование, очистку старых записей, разделение больших таблиц по партициям, не допускаем неограниченного роста «лог-таблиц» без TTL, явно выбираем стратегию удаления: soft delete (флаг) + фильтрация в запросах или hard delete, понимаем trade-off и особенности индексов и уникальных ограничений, храним время и даты в UTC, чётко разделяем `TIMESTAMP WITH TIME ZONE`/`WITHOUT`, не смешиваем локальные и UTC-времена, документируем, как работает время в системе, проектируем безопасность: отдельные пользователи/роли БД для разных компонент/сервисов, минимальные привилегии (только нужные таблицы и операции), не используем superuser/owner для приложения, шифруем данные на диске и по сети там, где это нужно, за особо чувствительные поля (PII, секреты) отвечаем колонко-уровневым шифрованием/внешними сервисами, включаем аудит доступа к критичным данным, строим наблюдаемость: логируем медленные запросы, отслеживаем количество соединений, блокировки, deadlock’и, используем connection pool на уровне приложения, не открываем и не закрываем соединения «на каждый запрос», тестируем БД не только через юнит-тесты приложения, но и через интеграционные тесты с реальной базой или testcontainers, прогоняем миграции на тестовом окружении, проверяем, что схема и данные совместимы с кодом, закладываем бэкапы и стратегии восстановления: автоматические регулярные бэкапы, тестовые восстановления, понимание RPO/RTO, документируем регламенты, проектируем схему и SQL так, чтобы новой команде было легко понять: где какие сущности, какие связи, какие инварианты, избегаем магии и implicit поведения, всё важное — явно, и на всех уровнях работы с SQL поддерживаем строгую схему, явные ограничения, предсказуемые транзакции, прозрачную производительность и возможость эволюции без разрушения данных.
### SQL / relational databases (English)
start not from tables and indexes but from the domain and the meaning of data, define which facts about the world you store, which questions you need to answer, and which invariants must always hold, design the schema from the domain model rather than from the ORM or UI forms, strive for each table to represent one clear type of entity or relationship, not a “bucket of everything”, normalize first (typically up to 3NF) to remove duplication and anomalies, then *intentionally* denormalize where it clearly improves performance or simplicity, don’t treat the database as a dumb key-value store when you care about integrity, choose data types deliberately: don’t store everything as `VARCHAR`/`TEXT`, use numeric types, proper date/time, boolean, enums/checks, respect ranges and precision, minimize use of `NULL`, default to NOT NULL and allow `NULL` only when it’s a meaningful state, define primary keys for all tables, use surrogate keys (INT/BIGINT/UUID) or natural keys where justified, avoid changing primary keys later unless absolutely necessary, always define foreign keys and referential integrity in the DB, don’t rely solely on the app to keep relationships valid, use `UNIQUE` and `CHECK` constraints for invariants and business rules, don’t put all rules only in code, model relationships explicitly: one-to-many via FKs, many-to-many via join tables, avoid over-generic “polymorphic” references that break integrity and make queries painful, follow a clear and consistent naming convention for tables, columns, indexes and keys, avoid cryptic three-letter abbreviations without context, think about multi-tenancy early: separate DB/schema per tenant or shared schema + `tenant_id`/RLS, design for isolation and migration convenience, treat schema migrations as part of the architecture: use a proper migration tool, keep schema versioned in the repo, never change prod tables ad hoc “in the console”, design migrations to be backward compatible so you can roll code out in stages (old code works with new schema and vice versa where possible), for big tables and critical systems design zero-downtime migrations: add new columns/tables, backfill in chunks, switch, then clean up, design transaction handling explicitly: understand isolation levels (read committed, repeatable read, serializable) and choose a default intentionally, set transaction boundaries at the application layer, keep transactions as short as possible, don’t run long analytical SELECTs inside them, avoid holding locks longer than needed, distinguish business errors from lock conflicts/deadlocks and implement retries where appropriate, design indexes based on queries, not hunches: first understand typical workloads, then create fitting indexes, use composite indexes and column order intentionally, avoid “one index per column”, watch index bloat and write costs, inspect execution plans with `EXPLAIN`/`EXPLAIN ANALYZE` instead of optimizing blindly, avoid dumping business logic into stored procedures and triggers “because we can”, use them for invariants, auditing, heavy batch work when they clearly win, and keep their complexity in check, always use parameterized queries/prepared statements in applications, never build SQL by string concatenation, treat SQL injection protection as a default, treat the ORM as a helper, not as the source of truth: know what SQL it generates, write explicit queries where needed, and don’t twist the data model just to please the ORM, design a data access layer (repositories/DAOs) as an explicit port, don’t let random code across the app fire ad-hoc SQL everywhere, think about consistency and performance: avoid N+1 queries, batch operations, use `JOIN`s/bulk inserts/updates where sensible, remember the DB is a shared constrained resource, don’t turn it into a black box hit for every tiny step, plan for data growth: archiving, retention policies, partitioning of large tables, avoid unbounded growth of “log tables” without TTL, consciously choose deletion strategy: soft delete (flag) + query filtering or hard delete, understand the trade-offs and their impact on indexes and uniqueness constraints, store time in UTC, be explicit about `TIMESTAMP WITH/WITHOUT TIME ZONE`, never mix local and UTC timestamps randomly, document how time works in the system, design security: separate DB users/roles for different components/services, least privilege access (only needed tables and operations), never run the app as superuser/owner, encrypt data at rest and in transit where needed, use column-level encryption or external services for highly sensitive data (PII, secrets), enable auditing for access to critical tables, build observability: log slow queries, track connection counts, locks and deadlocks, use a connection pool in the app, don’t open/close connections per query, provide health/ready endpoints and export DB metrics, test the DB not only via unit tests in the app but also via integration tests against a real DB or testcontainers, run migrations in test environments, ensure schema and data are compatible with code, plan backups and restore strategies: automatic regular backups, test restores, clear RPO/RTO, documented runbooks, design schema and SQL so a new engineer can understand which entities exist, how they relate, and which invariants are enforced, avoid magic and implicit behavior, make important things explicit, and at all levels of working with SQL maintain a strict schema, explicit constraints, predictable transactions, transparent performance, and the ability to evolve without breaking your data.
### React backend (Next.js / Remix / BFF / server components)
начинаем не с Next.js/Remix роутов, API handlers и server components, а с домена системы и бизнес-сценариев, формулируем, какую ценность даёт серверная часть: какие кейсы закрывает, какие данные агрегирует, какие внешние сервисы прячет от клиента, разделяем слои: домен (модели и правила), application/use-cases (команды/queries, оркестрация), infrastructure (БД, очереди, внешние API), web/transport (API routes, серверные actions, server components как представление), рассматриваем React/Next/Remix на сервере как презентационный и транспортный слой, а не как домен, не тащим `Request`, `Response`, `NextRequest`, cookies и детали роутинга в доменные модели, маппим HTTP/route params/body в DTO на границе handler/route, держим handlers максимально тонкими: извлекли данные из запроса → провалидировали → вызвали use-case → замапили результат в ответ/props для компонента, не пишем бизнес-логику в `app/api/*/route.ts` и `loader/action` в Remix, строим «backend for frontend» как отдельный слой, который адаптирует сырой backend/микросервисы под нужды React-фронта, а не просто проксирует запросы, агрегируем и нормализуем данные на сервере, чтобы фронт получал уже удобные модели, явно отделяем доменные объекты от transport DTO и от UI props, не протаскиваем сырые ORM-entities или ответы микросервисов прямо в компоненты, используем TypeScript интерфейсы/типы для доменных моделей и контрактов, не позволяем компонентам знать о форме данных в БД и конкретных REST/GraphQL схемах, проектируем server components/SSR как чистый presentation layer — они получают уже подготовленную view-model, а не сами бегают по БД и внешним API как попало, доступ к БД и внешним сервисам изолируем в репозиториях/клиентах в инфраструктурном слое, а не размазываем `prisma/knex/fetch` по route-handlers и компонентам, use-cases живут в application-слое и не зависят от Next/Remix API, их можно вызвать как из HTTP-handler, так и из server action, и из фонового job’а, конфигурацию и секреты (ключи, строки подключения, внешние URL) держим только в серверном окружении (`process.env` на сервере), чётко различаем server-env и client-env, никогда не помечаем секреты как `NEXT_PUBLIC_*` и не пробрасываем их в client bundle, держим auth/session/permissions как кросс-срез: middleware/loader/route handler проверяет контекст пользователя, домен получает уже валидированный `UserContext`, а не сам ковыряется в cookies и токенах, используем BFF-подход для интеграции с микросервисами: React-backend знает контракты внутренних сервисов, кэширует где надо, маппит ошибки и статусы в удобную для фронта форму, не отдаёт клиенту внутренние коды/структуры, проектируем API routes/server actions не как «каждый компонент вызывает свой маленький endpoint», а как осмысленные use-case операции с чётким контрактом, минимизируем чатовость и фан-аут, разделяем server state и UI state: на сервере агрегируем данные, на клиент отдаём ровно то, что нужно для отрисовки текущего экрана, думаем о SSR/SSG/ISR как о деталях инфраструктуры, а не домена: доменные и application-сервисы не должны знать, будет ли страница рендериться на сервере или на клиенте и как часто она регенерируется, проектируем обработку ошибок как часть публичного контракта: на уровне backend-for-frontend решаем, какие статусы и сообщения увидит клиент, используем единый формат ошибок, не вываливаем stacktrace и детали базы наружу, логируем важные события и ошибки на сервере (structured logs), добавляем correlation id и связываем их с фронтовыми запросами, проектируем кэширование (HTTP cache, revalidate, edge cache) осознанно: что можно кэшировать, на сколько, как инвалидировать, не кэшируем персональные данные без учёта auth контекста, следим за границами: UI-слой (компоненты) не знает про конкретный transport (REST/GraphQL), инфраструктура не знает про JSX, домен не знает ни про то, ни про другое, структурируем репозиторий по архитектуре и фичам, а не только по `app/` и `pages/`: выделяем папки для `domain`, `application`, `infra`, `ui`, не складываем всё в `lib/` и `utils/`, пишем юнит-тесты на домен и use-cases без React/Next, интеграционные — на handler’ы и API routes, e2e — на весь стек, держим React-backend тонким BFF-слоем над хорошо спроектированным доменом и внешними сервисами, чтобы можно было заменить UI (или наоборот, вынести BFF отдельно) без переписывания всего ядра, и на всех уровнях соблюдаем: чёткие слои, явные контракты, слабую связность и тестируемость.
start not from Next.js/Remix routes, API handlers and server components but from the system domain and business scenarios, define what value the server-side actually provides: which cases it covers, what data it aggregates, which external services it hides from the client, split into layers: domain (models and rules), application/use-cases (commands/queries, orchestration), infrastructure (DB, queues, external APIs), web/transport (API routes, server actions, server components as presentation), treat React/Next/Remix on the server as presentation and transport, not as the domain, keep `Request`, `Response`, `NextRequest`, cookies and routing details out of domain models, map HTTP/route params/body to DTOs at the handler boundary, keep handlers as thin as possible: extract params → validate → call a use-case → map result to an HTTP response or component props, never put business logic into `app/api/*/route.ts` or Remix `loader/action`, build a “backend for frontend” as a dedicated layer that adapts raw backend/microservices to React’s needs, not just a dumb proxy, aggregate and normalize data on the server so the frontend receives convenient models, clearly separate domain objects from transport DTOs and UI props, don’t push raw ORM entities or microservice responses directly into components, use TypeScript interfaces/types for domain models and contracts, don’t let components know about DB shape or exact REST/GraphQL schemas, treat server components/SSR as pure presentation: they receive prepared view models instead of doing random DB/external calls themselves, isolate DB/external access in repositories/clients in the infra layer, not scattered `prisma/knex/fetch` inside routes and components, place use-cases in the application layer, independent from Next/Remix APIs, callable from HTTP handlers, server actions or background jobs, keep configuration and secrets (keys, connection strings, external URLs) in server-only env (`process.env` on the server), distinguish server env from client env, never mark secrets `NEXT_PUBLIC_*` or leak them into the client bundle, handle auth/session/permissions as cross-cutting concern: middleware/loader/route handler validates user context, the domain receives a clean `UserContext` instead of fiddling with cookies and tokens, use a BFF-style integration with microservices: the React backend knows inner service contracts, applies caching where needed, maps statuses and errors to a client-friendly shape, doesn’t leak internal codes/structures, design API routes/server actions as meaningful use-case operations with clear contracts, not “one tiny endpoint per component”, minimize chattiness and fan-out, separate server state from UI state: aggregate on the server, send just enough for the current screen, treat SSR/SSG/ISR as infra details, not domain: domain and application services shouldn’t care whether a page is rendered on the server or client or how often it’s revalidated, treat error handling as part of the public contract: the backend-for-frontend decides which status codes and messages the client sees, use a unified error format, never expose stack traces or DB details to clients, log important events and errors on the server (structured logs) with correlation IDs linking to frontend requests, design caching (HTTP cache, revalidate, edge cache) consciously: what can be cached, for how long, and how it’s invalidated, don’t cache personalized data without accounting for auth context, keep boundaries clear: UI layer (components) doesn’t know the transport (REST/GraphQL), infra doesn’t know JSX, domain knows neither, structure the repo by architecture and features, not only `app/`/`pages/`: dedicate folders for `domain`, `application`, `infra`, `ui`, don’t throw everything into `lib/`/`utils/`, write unit tests for domain and use-cases without React/Next, integration tests for handlers/API routes, e2e tests for the whole stack, keep the React backend as a thin BFF layer over a well-designed domain and external services so you can swap the UI (or move the BFF out) without rewriting the core, and at every level maintain clear layers, explicit contracts, loose coupling and testability.
### React frontend (SPA / Next/CSR / client components)
начинаем не с компонентов, хуков и красивых UI-библиотек, а с домена фронтенда и пользовательских сценариев, формулируем, какие флоу и состояния реально нужны пользователю, какие данные должны быть на клиенте и зачем, строим архитектуру от use-cases и состояния, а не от страниц и роутов, разделяем слои: domain (модели и бизнес-правила, по возможности фреймворк-агностичные), application/state (use-cases, state management, эффекты), infrastructure (API-клиенты, storage, адаптеры к backend/BFF), presentation (React-компоненты, layout, дизайн-система), считаем backend/BFF просто внешней системой, на границе data-слоя маппим transport DTO в доменные модели, не протаскиваем сырые responses прямо в JSX, выстраиваем дерево компонентов от контейнеров/страниц к презентационным: контейнеры получают данные через application слой (hooks/services), подписываются на состояние, а вниз отдают только props, презентационные компоненты «тупые», не знают про fetch, router и global state, используем hooks и/или отдельные state-библиотеки (Zustand, Redux Toolkit, Recoil, Jotai, Zustand + TanStack Query и т.п.) осознанно, разделяем **server state** (данные с API) и **UI state** (выборы, фильтры, открытые модалки), server state ведём через специализированные инструменты (TanStack Query/RTK Query) с кэшем, рефетчем, статусами, а UI state храним локально в компонентах или feature-store, не складываем всё в один глобальный Redux «на всякий случай», используем Context только для действительно глобальных вещей (тема, текущий пользователь, конфиг), не превращаем Context в «второй Redux» и не делаем prop-drilling ради принципа, держим бизнес-логику в слоях hooks/use-cases и сервисов, а не в JSX тела компонентов, компоненты максимум orchestrate: вызвали hook → получили данные/действия → отрендерили, явно обрабатываем тройку состояний: loading / error / empty, не рисуем UI только для happy-path, выносим работу с API в отдельный слой (apiClient, repository, services), используем fetch/axios только там, добавляем туда обработку ошибок, ретраи, отмену запросов (AbortController), не размазываем HTTP-логку по компонентам, держим типы и модели в TypeScript, не оставляем всё `any`, описываем доменные модели и DTO, минуем магический «данные как пришли», проектируем маршрутизацию (React Router, Next App Router) как карту экранов и доменных разделов, не жёстко привязываем компоненты к конкретным URL (пусть знают о маршруте через router hooks/props, но не через прямое парсение `window.location`), для Next — чётко делим client/server components и не выносим тяжёлую логику и глобальный state в client без необходимости, выносим дизайн-систему и общие компоненты (Buttons, Inputs, Layout, Typography, Modal) в отдельный слой, не копипастим стили и верстку, используем tokens (цвета, spacing, типографика), поддерживаем светлую/тёмную тему и адаптивность, думаем об accessibility: семантический HTML, aria-атрибуты, фокус, клавиатурная навигация, не жертвуем доступностью ради красоты, планируем i18n: не хардкодим строки в JSX, используем ресурсы/файлы переводов, учитываем локали, форматы дат/чисел и правый-налево где нужно, конфигурацию фронта (base API URL, feature flags, режимы) выносим в env/config, не хардкодим в код, чётко различаем то, что можно зашить в bundle и то, что должно приехать с сервера, доступ к localStorage/sessionStorage/IndexedDB инкапсулируем в сервисах/hooks, а не дергаем прямо в каждом компоненте, строим архитектуру ошибок: ErrorBoundary для неожиданных падений, нормальные сообщения для ожидаемых бизнес-ошибок, логируем ошибки (Sentry/аналог) централизованно, не раскидываем `console.error` по всему проекту, следим за производительностью: используем React.memo/useMemo/useCallback там, где это оправдано, но не увлекаемся преждевременной микрооптимизацией, разбиваем бандл (code splitting, lazy), не тащим весь UI в первый чанк, внимательно относимся к зависимостям: не подключаем по три разных state-менеджера и две UI-библиотеки ради эксперимента, ограничиваем стек, чтобы сократить сложность, строим структуру каталогов по архитектуре и фичам, а не только `components/pages/utils`: выделяем `features/*` (по доменам), внутри — `ui`, `model/state`, `api`, `lib`, избегаем папок-свалок `helpers`, пишем юнит-тесты для критичной бизнес-логики и hooks, компонентные/интеграционные тесты для ключевых фич (Testing Library), e2e-тесты (Cypress/Playwright) для основных пользовательских сценариев, поддерживаем React-фронт как тонкий, предсказуемый слой над BFF/бэкендом и доменном, который можно развивать и упрощать без переписывания в ноль, и во всём React-коде фронта держим: строгие границы слоёв, явные контракты, слабую связность между фичами, управляемое состояние и тестируемость.
start not from components, hooks and pretty UI libraries, but from the frontend domain and user scenarios, define which flows and states users actually need, which data must live on the client and why, design architecture from use-cases and state rather than from pages and routes, split into layers: domain (models and business rules, ideally framework-agnostic), application/state (use-cases, state management, effects), infrastructure (API clients, storage, adapters to backend/BFF), presentation (React components, layout, design system), treat backend/BFF as just an external system, map transport DTOs to domain models at the data boundary, don’t feed raw responses directly into JSX, structure the component tree from container/pages down to presentational components: containers get data via application layer (hooks/services), subscribe to state and pass props down, presentational components are “dumb” — no fetch, no router, no global state, use hooks and/or state libs (Zustand, Redux Toolkit, Recoil, Jotai, TanStack Query etc.) intentionally, separate **server state** (data from APIs) from **UI state** (filters, selections, modal visibility), manage server state through specialized tools (TanStack Query/RTK Query) with caching, refetching and status flags, and keep UI state local in components or feature stores, don’t dump everything into one giant global Redux “just because”, use Context only for truly global concerns (theme, current user, config), don’t turn Context into a second Redux or push props deep just for fun, keep business logic in hooks/use-cases and services, not inside JSX bodies, components should orchestrate at most: call hooks → receive data/actions → render, always handle the triad: loading / error / empty, not just the happy path, centralize API calls in a dedicated layer (api clients, repositories, services), use fetch/axios only there, add error handling, retries, cancellation (AbortController), don’t scatter HTTP logic across components, define types/models in TypeScript, avoid `any`, express domain models and DTOs explicitly, not “data as it came”, design routing (React Router, Next App Router) as a map of screens and domain sections, don’t hard-wire components to URLs (let them use router hooks/props, not `window.location` parsing), for Next, clearly separate client and server components and don’t move heavy logic and global state to the client without need, extract a design system and shared components (Button, Input, Layout, Typography, Modal) into their own layer, avoid copy-pasting styles/markup, use tokens (colors, spacing, typography), support light/dark themes and responsive layouts, care about accessibility: semantic HTML, aria attributes, focus, keyboard navigation, don’t trade a11y for looks, plan i18n: no hardcoded strings in JSX, use translation resources/files, respect locales, date/number formats and RTL where needed, move frontend configuration (base API URL, feature flags, modes) into env/config, don’t hardcode in code, clearly distinguish what can be baked into the bundle vs what must come from the server, encapsulate localStorage/sessionStorage/IndexedDB access in services/hooks, not scattered across components, design error architecture: ErrorBoundary for unexpected crashes, user-friendly messages for expected business errors, log errors (Sentry/analog) centrally, not via random `console.error`, watch performance: use React.memo/useMemo/useCallback where they pay off, but avoid premature micro-optimizations, split the bundle (code splitting, lazy loading), don’t ship the entire UI in the first chunk, manage dependencies carefully: don’t pull three different state managers and two UI kits “to try them all”, limit the stack to reduce complexity, structure folders by architecture and features, not just `components/pages/utils`: use `features/*` by domain, each with `ui`, `model/state`, `api`, `lib`, avoid dumping grounds like `helpers`, write unit tests for critical business logic and hooks, component/integration tests (Testing Library) for key features, e2e tests (Cypress/Playwright) for main user flows, keep the React frontend as a thin, predictable layer over the BFF/backend and domain so you can evolve and simplify it without rewriting from scratch, and throughout the React frontend code maintain strict layer boundaries, explicit contracts, loose coupling between features, controlled state and solid testability.
### Visual Studio extensions (рус)
начинаем не с VSIX-проекта и магии VS SDK, а с **домена расширения и пользы для разработчика**, чётко формулируем: кому помогает расширение, какие сценарии упрощает, какие боли снимает, какие артефакты читает/меняет (код, решения, проекты, настройки), описываем основные user flow: что делает пользователь, какие шаги, какой результат, разделяем слои: **домен** (модель того, *что* делает расширение), **application/use-cases** (команды, сценарии, оркестрация), **infrastructure** (VS SDK, Roslyn, файловая система, сеть, настройки), **presentation** (ToolWindow, меню, контекстные команды, диалоги, подсветка, подсказки), рассматриваем Visual Studio и её API как **край системы**, а не как домен, не тащим DTE, `EnvDTE.Project`, `IVsSolution`, `IWpfTextView` прямо в доменные классы, маппим объекты VS в свои DTO/модели на границах, строим архитектуру вокруг **use-cases** расширения (RefactorXCommand, GenerateSomethingForSelection, AnalyzeSolutionCommand), а не вокруг «кучи разрозненных CommandHandlers», даём каждому use-case одну чёткую ответственность, избегаем god-объектов типа `ExtensionService` который знает и делает всё, доменная логика не должна знать, откуда пришли данные — из текущего документа, контекстного меню, hotkey или меню Tools, отделяем **домен** и **VS-инфраструктуру**: VS SDK, MEF, Roslyn, редактор, Project System — всё это адаптеры, а не «нормальные» зависимости для core-логики, проектируем собственные абстракции/интерфейсы: `ISolutionModel`, `IProjectModel`, `IDocumentModel`, `ICodeSelection`, `IWorkspaceService`, которые реализуются через VS-API в инфраструктурном слое, используем `AsyncPackage` как точку входа и composition root: регистрируем команды, сервисы, адаптеры, но в домен DI заходит через конструктор, а не через статические singletons, избегаем использования DTE и глобальных статических объектов везде, DTE — COM-слой, стараемся ограничить его во внутренних адаптерах, по возможности используем новые API (AsyncPackage, `IAsyncServiceProvider`, `JoinableTaskFactory`, Roslyn Workspace, CPS), всегда думаем о **UI-потоке**: не блокируем его длинными операциями (анализ решений, обход файлов, сетевые запросы), всё тяжёлое выполняем асинхронно (Tasks/async/await, JTF), отображаем прогресс (Status Bar, Task Center, progress dialog), поддерживаем отмену (CancellationToken), проектируем расширение «не мешающим»: при простом открытии решения и наборе кода оно не должно заметно тормозить VS, никакого постоянного опроса и тяжёлых таймеров, слушаем события решения/документов (SolutionEvents, Roslyn Workspace events) и реагируем минимально необходимым образом, не пересчитываем всё заново без причины, при работе с редактором моделируем свои сущности: `CurrentDocument`, `SelectedSpan`, `CodeRegion`, а VS-объекты (`ITextBuffer`, `SnapshotSpan`) прячем в адаптерах, для Roslyn-анализаторов и refactorings по возможности используем **Roslyn-механизм (analyzers, code fixes, refactorings)**, а не «ручное» парсирование текста, разделяем: VSIX как оболочка/host + Roslyn-пакеты как чистый анализ, отделяем UI-элементы: ToolWindow, диалоги, опции — в presentation-слой, там только привязки и отображение, вся логика — в ViewModel/презентерах/use-cases, не пишем бизнес-правила в обработчиках кликов, используем паттерн MVVM/MVP для сложных UI частей (ToolWindow, сложные диалоги), View знает только про биндинги и команды, а не про VS-API, храним настройки расширения через `WritableSettingsStore`/OptionsPage/`UserSettings` в отдельном слое конфигурации, не зашиваем пути, URL, ключи и магические значения в код, следим за **совместимостью версий Visual Studio**: явно выбираем минимальную версию VS, таргет фреймворка, API-зависимости, проектируем расширение так, чтобы новые возможности VS использовались, но не ломали старые версии без явного повышения минимальной версии, внимательно относимся к **жизненным циклам**: пакет инициализируется, когда нужен, ToolWindow создаётся при первом открытии, слушатели событий отписываются при закрытии, не допускаем утечек памяти из-за подписок и кешей, логируем ключевые события, ошибки и длительные операции (TraceListener, собственный лог, ETW/ActivityLog), но не зашумляем лог бесполезными сообщениями, ошибки не скрываем молча: в лог — полный stacktrace, пользователю — аккуратное сообщение/notification, проектируем архитектуру так, чтобы большую часть логики можно было тестировать **без запуска VS**: домен и use-cases — чистые .NET-классы, для VS-адаптеров пишем интеграционные тесты с использованием VS SDK / экспериментального инстанса, изолируем взаимодействие с файловой системой и сетью в инфраструктурных сервисах (`IFileSystem`, `IHttpClient`), чтобы можно было мокать их в тестах, структура проектов/solution отражает архитектуру: отдельные проекты для `Extension.Domain`, `Extension.Application`, `Extension.Infrastructure.VS`, `Extension.UI`/`Extension.Vsix`, а не один VSIX-проект, в котором всё подряд, придерживаемся **guidelines Microsoft по extension-разработке**: уважаем настройки пользователя, не ломаем key bindings, не перехватываем всё подряд, не вмешиваемся в чужой код, требуем только те capabilities, которые реально нужны, продумываем **update-story**: при изменении функционала и схемы настроек сохраняем обратную совместимость, мигрируем настройки при обновлении, не ломаем поведение для существующих пользователей без явного изменения, используем **ADRs** или короткие архитектурные заметки, чтобы фиксировать: как мы интегрируемся с VS, какие API используем, как устроен lifecycle, как устроены слои, закладываем в архитектуру возможность переноса доменной логики в другие хосты (например, отдельный CLI, Roslyn analyzer без VSIX, Rider plugin в будущем), поэтому VS-зависимости — только в адаптерах, во всём коде расширения поддерживаем: строгие границы между доменом и VS-API, минимальную связность, явные контракты (интерфейсы, события, модели) и высокий уровень тестируемости и предсказуемости производительности.
---
### Visual Studio extensions (English)
start not from a VSIX project and VS SDK magic, but from the **extension domain and developer value**, clearly define who the extension is for, which scenarios it streamlines, which pains it removes, and what artifacts it reads/changes (code, solutions, projects, settings), describe key user flows: what the user does, step by step, and what outcome they get, split into layers: **domain** (what the extension does conceptually), **application/use-cases** (commands, orchestration, scenarios), **infrastructure** (VS SDK, Roslyn, filesystem, network, settings), **presentation** (tool windows, menus, context commands, dialogs, adornments, tags, suggestions), treat Visual Studio and its APIs as the **outer boundary**, not as your domain, don’t pass DTE, `EnvDTE.Project`, `IVsSolution`, `IWpfTextView` deep into domain classes, map VS objects to your own DTOs/models at the boundaries, design architecture around **use-cases** (RefactorXCommand, GenerateSomethingForSelection, AnalyzeSolutionCommand) instead of a random pile of CommandHandlers, give each use-case a single focused responsibility, avoid god objects like `ExtensionService` that know and do everything, keep domain logic ignorant of how it was triggered — toolbar, hotkey, context menu, or Tools menu, separate **domain** from **VS infrastructure**: VS SDK, MEF, Roslyn, editor, project system are adapters, not “normal” dependencies for core logic, design your own abstractions/interfaces such as `ISolutionModel`, `IProjectModel`, `IDocumentModel`, `ICodeSelection`, `IWorkspaceService`, implemented by VS-backed adapters in the infra layer, use `AsyncPackage` as the entry point and composition root: register commands, services, adapters there, but inject dependencies into domain via constructors instead of static singletons, avoid overusing DTE and global static objects; DTE is a COM layer, prefer newer APIs (AsyncPackage, `IAsyncServiceProvider`, `JoinableTaskFactory`, Roslyn Workspace, CPS) where possible, always think about the **UI thread**: never block it with heavy work (full solution analysis, file traversal, network calls), run heavy logic asynchronously (Tasks/async/await, JTF), show progress (Status Bar, Task Center, progress dialogs) and support cancellation with `CancellationToken`, design the extension to be **non-intrusive**: simply opening a solution and typing should not feel slower because your extension is installed, no constant polling, no heavy timers, listen to solution/document events (SolutionEvents, Roslyn Workspace events) and react minimally, don’t recompute everything without reason, when working with the editor, model your own entities (CurrentDocument, SelectedSpan, CodeRegion) and hide VS types (`ITextBuffer`, `SnapshotSpan`) behind adapters, for analyzers and refactorings use **Roslyn analyzers/code fixes/refactorings** whenever possible instead of manually parsing text, separate VSIX as host/shell from Roslyn packages as pure analysis, keep UI: tool windows, dialogs, options pages in the presentation layer; they only bind/render, while all real logic lives in ViewModels/presenters/use-cases, don’t embed business logic in click handlers, use MVVM/MVP for complex UI parts (tool windows, complex dialogs), View only knows about bindings and commands, not VS APIs, store extension settings via `WritableSettingsStore`/OptionsPage/user settings in a distinct configuration layer, don’t hardcode paths, URLs, keys or magic numbers, pay attention to **Visual Studio version compatibility**: explicitly choose minimum VS version, target framework and API dependencies, design the extension so new VS features are used, but older VS versions still work until you intentionally bump the minimum, handle **lifecycles** carefully: the package is initialized only when needed, tool windows created on first use, event listeners unsubscribed when closing, avoid memory leaks via subscriptions and caches, log key events, failures and long-running operations (TraceListener, your own logger, ActivityLog/ETW), but don’t flood logs with noise, don’t swallow errors: log full stack traces, show a friendly message/notification to the user, design architecture so most logic can be tested **without launching VS**: domain and use-cases are plain .NET classes, VS adapters can be covered by integration tests using the VS SDK/test harness, isolate filesystem/network access in infrastructure services (`IFileSystem`, `IHttpClient`) so they can be mocked in tests, structure the solution to reflect your architecture: separate projects for `Extension.Domain`, `Extension.Application`, `Extension.Infrastructure.VS`, `Extension.UI`/`Extension.Vsix`, not one VSIX project with everything thrown in, follow **Microsoft extension guidelines**: respect user settings, don’t break key bindings, don’t hijack everything, don’t interfere with others, request only the capabilities you actually need, plan your **update story**: when behavior and settings schemas change, keep backward compatibility and migrate settings for existing users instead of silently breaking them, use **ADRs** or short architecture notes to record decisions: how you integrate with VS, which APIs you rely on, lifecycle design, layer structure, build in the option to move your domain logic to other hosts in the future (CLI, standalone Roslyn analyzers, Rider plugin), so VS dependencies stay confined to adapters, and throughout the extension code maintain strict boundaries between domain and VS APIs, minimal coupling, explicit contracts (interfaces, events, models), and high testability and predictable performance.
### chrome extensions
начинаем не с копипаста чужого `manifest.json` и запроса всех возможных permissions, а с понимания домена расширения и пользы для пользователя, формулируем одну–две ключевые боли, которые будет решать расширение, описываем пользовательские сценарии пошагово (что делает человек, где он сейчас в браузере, какой результат должен получить), проектируем архитектуру от домена и сценариев, а не от API Chrome, разделяем слои: домен (чистая логика, модель данных), application/use-cases (оркестрация действий, обработка событий), infrastructure (обёртки над `chrome.*` API, storage, network), presentation (popup, options, side panel, страницы, которые видит пользователь), рассматриваем background/service worker как часть инфраструктуры, а content scripts как адаптер между веб-страницей и нашим доменом, не смешиваем бизнес-логику с прямыми вызовами `chrome.tabs`, `chrome.scripting`, `chrome.storage` и прямой работой с DOM в одном месте, инкапсулируем все вызовы Chrome API в небольших и тестируемых адаптерах, строим взаимодействие между частями через явное message passing (background ⇄ content ⇄ UI), описываем контракт сообщений явно (типы, payload, кто кому шлёт), избегаем «магических строк» для типов сообщений, держим content scripts максимально лёгкими: только чтение/мутирование DOM и пересылка данных в домен, бизнес-логика живёт в отдельном слое и может быть переиспользована, учитываем особенности Manifest V3: фоновая часть — это service worker без постоянного процесса, храним важное состояние в `chrome.storage` или на стороне страницы, пересчитываем дорогое только по событиям, а не «каждую миллисекунду», закладываем обработку пробуждения/засыпания service worker, проектируем расширение с принципом наименьших привилегий: запрашиваем только те permissions и host-разрешения, которые реально нужны, избегаем `"<all_urls>"` и широких URL-масок без необходимости, используем `activeTab` там, где достаточно временного доступа, не добавляем новые permissions без пересмотра UX и безопасности, держим `manifest.json` как источник правды: минимальный набор полей, понятные названия, чётко описываем permissions, host-patterns, действия (action/browser_action, commands, devtools), отделяем логику от конкретной формы UI: popup, page action, side panel должны быть тонкими оболочками над use-cases, используем модульную структуру и по возможности TypeScript, чтобы явно типизировать данные и сообщения, строим структуру каталогов по архитектуре: `domain`, `application`, `infrastructure/chrome`, `ui/popup`, `ui/options`, `content`, а не одну папку `scripts` и кучу файлов `utils.js`, проектируем безопасность: никогда не вставляем произвольный удалённый код, не используем `eval` и динамически сгенерированные скрипты, учитываем CSP Chrome Web Store, минимизируем доступ к cookies и пользовательским данным, не читаем и не отправляем лишнюю информацию, явно описываем в privacy-policy, что делаем с данными, используем `chrome.storage` осмысленно: определяем схему, версионируем её, при обновлениях аккуратно мигрируем данные, не превращаем storage в свалку ключей без структуры, закладываем локализацию через `_locales`, не хардкодим строки в код там, где они должны быть переведены, проектируем обработку ошибок везде: промисы `chrome.*` не игнорируем, логируем ошибки в dev, в production показываем понятные пользователю сообщения, не ломаем тихо функционал, учитываем многократные вкладки, окна, инкогнито: state не должен «ломаться» при нескольких экземплярах, тестируем расширение на медленных машинах и с большим количеством вкладок, следим за производительностью content scripts (минимум тяжелых операций на каждом `mutation`/`scroll`/`input`), уважаем другие расширения и сайты: не переопределяем глобальные стили без неймспейса, не ломаем верстку, не блокируем события пользователя без явной причины, автоматизируем сборку (bundler, manifest-generation, icons), подписку и деплой, документируем архитектурные решения и контракты (какие сообщения, какие permissions, какие точки интеграции), проектируем расширение так, чтобы ядро логики можно было вынести в отдельный пакет и при необходимости переиспользовать (например, в другой браузер или в web-приложение), и на всех уровнях разработки Chrome-расширения поддерживаем строгие границы между доменом, Chrome API и UI, явные контракты, минимальные permissions, слабую связность и высокую тестируемость.
start not from copy-pasting someone else’s `manifest.json` and requesting every possible permission, but from the extension’s domain and the value it brings to users, clearly define one or two core pains the extension will solve, describe concrete user flows step by step (where the user is in the browser, what they click, what result they expect), design the architecture from domain and use-cases rather than from Chrome APIs, split your code into layers: domain (pure logic, data model), application/use-cases (orchestration, event handling), infrastructure (wrappers over `chrome.*` APIs, storage, network), presentation (popup UI, options page, side panel, any HTML UIs), treat the background/service worker as part of infrastructure and content scripts as adapters between web pages and your domain, avoid mixing business logic with direct `chrome.tabs`, `chrome.scripting`, `chrome.storage` calls and hand-written DOM manipulation in the same place, encapsulate all Chrome API usage into small, testable adapters, communicate between pieces via explicit message passing (background ⇄ content ⇄ UI), define message contracts clearly (types, payload shape, who talks to whom), avoid magical string literals for message types, keep content scripts as thin as possible: they only read/mutate the page DOM and forward data to the domain, real business rules live in a separate layer that can be reused, account for Manifest V3 specifics: background is a service worker without a persistent process, store important state in `chrome.storage` or on the page side, recompute expensive things only on events, not “every millisecond”, design for worker wake/sleep cycles, apply least-privilege security: request only the permissions and host matches you truly need, avoid `"<all_urls>"` and broad URL patterns unless strictly justified, use `activeTab` where temporary access is enough, don’t add new permissions without revisiting UX and security, treat `manifest.json` as the single source of truth: minimal but clear fields, explicit permissions, host patterns and capabilities (action/browser_action, commands, devtools, side panel), keep UI shells (popup, page action, side panel) thin and built on top of use-cases, use a modular structure and, if possible, TypeScript to strongly type data and messages, structure folders by architecture: `domain`, `application`, `infrastructure/chrome`, `ui/popup`, `ui/options`, `content`, instead of one `scripts` folder full of `utils.js`, design for security: never inject arbitrary remote code, don’t use `eval` or dynamically constructed scripts, respect Chrome Web Store CSP, minimize access to cookies and user data, don’t read or send more than necessary, and be explicit in your privacy policy about what you do, use `chrome.storage` deliberately: define a schema, version it, migrate data carefully on updates, don’t turn storage into a junk drawer of random keys, build localization via `_locales`, avoid hardcoding user-visible strings in code where they should be translated, treat error handling as a first-class concern: don’t ignore `chrome.*` promises or callbacks, log errors in development, show clear user-facing messages in production instead of silently breaking features, account for multiple tabs, windows and incognito: state must not fall apart when several instances are active, test the extension on slow machines and with many open tabs, watch content script performance (minimal heavy work on `mutation`/`scroll`/`input`), respect other extensions and sites: don’t override global styles without namespacing, don’t break layouts, don’t block user events without strong reasons, automate build (bundler, manifest generation, icons), packaging and publishing, document architectural decisions and contracts (which messages exist, which permissions are used, where integration points are), design the extension so that the core logic can live in its own package and be reused (e.g. in another browser or a web app) if needed, and across all Chrome extension code keep strict boundaries between domain, Chrome APIs and UI, explicit contracts, minimal permissions, loose coupling and high testability.
### Python backend
начинаем не с выбора фреймворка (Django/FastAPI/Flask) и настройки `requirements.txt`, а с понимания домена и бизнес-сценариев, формулируем какие задачи реально решает сервис, какие данные он хранит и отдаёт, какие потоки действий у пользователя и у внешних систем, проектируем архитектуру от домена и use-cases, а не от роутов и middleware, разделяем слои: домен (модели и бизнес-правила), application/use-cases (команды/queries, оркестрация сценариев), infrastructure (БД, очереди, внешние API, файловая система), web/transport (HTTP, gRPC, WebSocket), рассматриваем Django/FastAPI/Flask как транспортный и DI-слой, а не как домен, не тащим `Request`, `Response`, `HttpRequest`, `Depends`, `APIView` и ORM-модели во внутренние доменные правила, маппим HTTP-запросы в DTO на границе контроллеров/роутов/вьюх, держим вьюхи и роуты максимально тонкими: разобрали входящие данные, провалидировали, вызвали use-case, замапили результат в ответ, запрещаем бизнес-логике жить внутри сериализаторов, схем Pydantic, Django-вьюх и роут-функций, явно разделяем domain-модели и ORM-модели (Django models, SQLAlchemy), считаем ORM частью инфраструктуры, не протаскиваем ORM-сущности через все слои и не сериализуем их напрямую в JSON, строим репозитории как абстракции поверх ORM/сырых SQL, домен зависит от интерфейсов, а не от конкретной библиотеки, программируем против абстракций, внедряем зависимости явно: через конструкторы/фабрики/функции-композицию, избегаем скрытых синглтонов, глобальных модулей с состоянием и обращения к `from app import db` из произвольных мест, main/composition root знает, как собрать объекты, а домен — нет, используем типизацию (Python typing, Pydantic модели) осмысленно, описываем контракты явно, не оставляем критичный код с `Any` и динамикой «как попало», выносим конфигурацию (строки подключения, секреты, URL) в env и конфиги, не хардкодим их в коде, следим за разделением окружений (dev/test/stage/prod), проектируем API-контракты осознанно: ресурсы, методы, коды ответов, структуры ошибок, версионирование, не ломаем схемы молча, валидируем вход на границе (Pydantic, DRF serializers, marshmallow) и дублируем инварианты в домене, не полагаемся на один слой валидации, обрабатываем ошибки как часть публичного контракта: разделяем бизнес-ошибки и технические, проектируем единый формат error-response, не вываливаем stacktrace и детали БД наружу, используем middleware/exception handlers для логирования и маппинга исключений в ответы, думаем о безопасности с самого начала: аутентификация (JWT/OAuth2/сессии), авторизация (роли, permissions в домене), защита от инъекций, CSRF там где надо, rate limiting и защита от brute-force, минимальные права у сервисных аккаунтов, проектируем работу с БД через транзакции и репозитории, понимаем уровни изоляции и их влияние, не держим транзакции открытыми под долгими операциями, отделяем операции чтения и записи там, где это полезно (CQRS, отдельные query-сервисы и простые проекции), избегаем N+1 запросов, используем `select_related/prefetch` и явные join’ы, выносим работу с внешними сервисами в клиентские адаптеры, не размазываем `requests/httpx` по коду, добавляем таймауты, ретраи, circuit breaker, думаем об идемпотентности операций, логируем важные события структурировано (logging, JSON-логгеры), не логируем секреты, добавляем correlation id, строим наблюдаемость: метрики (Prometheus/StatsD), health-check endpoints, трассировку запросов, структуру пакетов строим по архитектуре и доменам, а не по «views/models/utils», выделяем `domain`, `application`, `infrastructure`, `presentation/web`, избегаем папок-свалок `helpers` и `common`, пишем юнит-тесты на домен и use-cases без фреймворка и без БД, интеграционные тесты — на web-слой и репозитории (pytest + тестовая БД/Testcontainers), встраиваем тесты в CI, проектируем миграции схемы (Alembic, Django migrations) и откаты как часть архитектуры, документируем существенные архитектурные решения (ADRs), держим Python backend простым, явным, с чёткими границами слоёв, слабой связностью, минимальным количеством «магии» фреймворков и высокой тестируемостью, чтобы можно было развивать его эволюционно, а не переписывать каждый год.
start not from picking a framework (Django/FastAPI/Flask) and tweaking `requirements.txt`, but from understanding the domain and business scenarios, define what problems the service actually solves, what data it stores and exposes, what flows users and external systems go through, design the architecture from domain and use-cases rather than from routes and middleware, split into layers: domain (models and business rules), application/use-cases (commands/queries, orchestration), infrastructure (DB, queues, external APIs, filesystem), web/transport (HTTP, gRPC, WebSocket), treat Django/FastAPI/Flask as transport and DI layers, not as your domain, keep `Request`, `Response`, `HttpRequest`, `Depends`, `APIView` and ORM models out of core domain rules, map HTTP requests to DTOs at the controller/view/router boundary, keep views and routes as thin as possible: parse input, validate, call a use-case, map result to a response, forbid business logic living inside serializers, Pydantic schemas, Django views and route functions, clearly separate domain models from ORM entities (Django models, SQLAlchemy), treat ORM as infrastructure, don’t drag ORM entities through all layers or serialize them directly to JSON, build repositories as abstractions on top of ORM/raw SQL, let the domain depend on interfaces, not specific libraries, code against abstractions, inject dependencies explicitly via constructors/factories/composition functions, avoid hidden singletons, global stateful modules and random `from app import db` usage, let main/composition root assemble objects while the domain stays unaware, use typing (Python typing, Pydantic models) deliberately, define contracts clearly, avoid leaving critical code as `Any` and wild dynamic behavior, move configuration (connection strings, secrets, URLs) into env/config files, don’t hardcode them in code, respect environment separation (dev/test/stage/prod), design API contracts intentionally: resources, methods, status codes, error structures, versioning, don’t break schemas silently, validate input at the edge (Pydantic, DRF serializers, marshmallow) and duplicate key invariants in the domain, don’t rely on a single validation layer, treat errors as part of the public contract: distinguish business vs technical errors, design a unified error-response format, don’t expose stack traces or DB details, use middleware/exception handlers to log and map exceptions to responses, think about security from day one: authentication (JWT/OAuth2/sessions), authorization (roles, domain permissions), protection against injections, CSRF where relevant, rate limiting and brute-force protection, least privilege for service accounts, design DB access via transactions and repositories, understand isolation levels and their impact, don’t keep transactions open during long operations, separate reads and writes where useful (CQRS, dedicated query services and simple projections), avoid N+1 queries, use `select_related/prefetch` and explicit joins, encapsulate external service calls in client adapters, don’t scatter `requests/httpx` everywhere, add timeouts, retries, circuit breakers, think about idempotent operations, log important events with structured logging, never log secrets, add correlation IDs, build observability: metrics (Prometheus/StatsD), health-check endpoints, trace IDs across calls, structure packages by architecture and domain, not by “views/models/utils”, extract `domain`, `application`, `infrastructure`, `presentation/web`, avoid dumping grounds like `helpers` and `common`, write unit tests for domain and use-cases without frameworks or DB, use integration tests for web layer and repositories (pytest + test DB/Testcontainers), wire tests into CI, design schema migrations (Alembic, Django migrations) and rollbacks as part of the architecture, record key architectural decisions (ADRs), keep the Python backend simple, explicit, with clear layer boundaries, loose coupling, minimal framework “magic” and high testability, so it can evolve incrementally instead of being rewritten every year.
### Python frontend (server-driven UI, templates, Streamlit/Dash/Gradio, PyScript/desktop)
начинаем не с выбора фреймворка (Jinja/Django templates, Streamlit, Dash, Gradio, PyQt/Tkinter, PyScript), а с домена фронтенда и пользовательских сценариев, формулируем, какие экраны, формы, отчёты и интерактивные элементы действительно нужны пользователю, какие данные показываем и какие действия разрешаем, строим архитектуру от use-cases и состояния, а не от набора виджетов и страниц, разделяем слои: домен (модели и бизнес-правила, по возможности фреймворк-агностичные), application/state (use-cases, управление состоянием, координация запросов), infrastructure (клиенты к backend/API, доступ к файлам/БД, кэш, окружение), presentation (шаблоны, компоненты UI, виджеты, layout), рассматриваем Python UI-фреймворк (шаблоны в Django/Flask, Streamlit/Dash/Gradio, PyQt/Fyne) как оболочку, а не как место хранения бизнес-логики, не пишем доменные правила прямо внутри шаблонов Jinja и callback’ов Dash/Streamlit, выносим логику в отдельные функции/сервисы и вызываем из обработчиков, держим представление «тупым»: шаблоны и виджеты только форматируют уже подготовленные данные и пробрасывают события вверх, а не принимают бизнес-решения, чётко разделяем server state (данные, пришедшие из backend/БД) и UI state (выбор фильтра, активный таб, открытая модалка), не складываем всё в один глобальный словарь/модуль со стейтом, используем простые структуры данных (dataclass, Pydantic модели, типизированные словари) для состояния, моделируем ввод/вывод как команды и ответы, а не произвольный набор параметров, при серверных шаблонах (Django, Jinja) готовим view-model в обработчике: собираем доменные данные, приводим к удобному формату, только потом передаём в шаблон, не тащим внутрь шаблонов ORM-объекты с кучей логики и ленивых запросов, следим за тем, чтобы шаблоны не выполняли тяжёлую работу и не делали запросы к БД, при использовании Dash/Streamlit/Gradio следим, чтобы callback’и были тонкими: минимум side-effects, максимум вызовов чистых функций и сервисов, код в callback’ах — это склейка UI-событий с доменными use-cases, а не сама бизнес-логика, избегаем использования глобальных переменных и синглтонов с состоянием между запросами, используем контекст сессии/пользователя, отдельные хранилища (кэш, БД, файлы) и явную сериализацию, проектируем конфигурацию фронтенда отдельно: base URL backend’а, фичи-флаги, режимы, не хардкодим их в шаблонах и коде, выносим в env и конфиги, думаем о безопасности UI: не вставляем неэкранированный пользовательский ввод, используем escaping в шаблонах, защищаем чувствительные действия CSRF-токенами там, где есть формы и POST-запросы, не засовываем секреты в HTML и JS, для PyScript и других Python-в-браузере подходов чётко разделяем то, что может жить на клиенте (безопасная логика, визуализация, локальные вычисления) и то, что должен делать backend (права доступа, чувствительные данные, операции с БД), не переносим авторизацию и критичный контроль на клиентский Python, при desktop UI (PyQt, Tkinter, Kivy и др.) используем паттерны вроде MVVM/MVP: View показывает, Presenter/ViewModel управляет состоянием и вызывает use-cases, домен не знает про виджеты, всё взаимодействие идёт через интерфейсы/сервисы, навигацию между экранами/оконцами инкапсулируем в отдельный слой, view не создают напрямую новые окна «из ниоткуда», доступ к файлам, сети и БД инкапсулируем в инфраструктурном слое, UI вызывает методы сервисов, а не работает с `os`, `requests`, SQL напрямую, закладываем i18n: выносим строки интерфейса в ресурсы/файлы локализации, не хардкодим текст по коду, учитываем локали при форматировании дат/чисел, проектируем обработку ошибок как часть UX: показываем пользователю понятные сообщения, не даём приложению падать с трассировкой в интерфейсе, логируем ошибки и неожиданные ситуации отдельно, тестируем бизнес-логику и application/state-слой отдельно от UI (юнит-тесты чистых функций и сервисов), UI-ток (шаблоны, callback’и) по необходимости покрываем интеграционными/сценарными тестами, следим за производительностью: не делаем тяжёлые вычисления и большие запросы в UI-треде (для desktop), не рендерим лишнее и не генерируем гигантские HTML-страницы без пагинации/ленивой загрузки, структуру проекта строим по архитектуре и фичам, а не по «views/templates/utils», выделяем `domain`, `app/state`, `infra`, `ui`, избегаем расползания кода по случайным модулям, держим Python frontend тонким слоем над backend и доменом, с чёткими границами слоёв, минимальной связностью, сильной типизацией данных и высокой тестируемостью, чтобы UI можно было изменять и переиспользовать без переписывания всей логики.
start not from picking a framework (Jinja/Django templates, Streamlit, Dash, Gradio, PyQt/Tkinter, PyScript) but from the frontend domain and user scenarios, define which screens, forms, reports and interactive elements users actually need, what data you show and what actions you allow, design architecture from use-cases and state rather than from a bag of widgets and pages, split into layers: domain (models and business rules, ideally framework-agnostic), application/state (use-cases, state management, coordination of requests), infrastructure (clients to backend/APIs, file/DB access, caching, environment), presentation (templates, UI components, widgets, layout), treat the Python UI framework (templates in Django/Flask, Streamlit/Dash/Gradio, PyQt/Fyne, etc.) as a shell, not as the place to store business logic, don’t write domain rules directly inside Jinja templates or Dash/Streamlit callbacks, move logic into separate functions/services and call them from handlers, keep the view “dumb”: templates and widgets only format prepared data and bubble up events, they don’t make business decisions, clearly separate server state (data coming from backend/DB) from UI state (selected filter, active tab, open modal), avoid dumping everything into a single global dict/module, use simple data structures (dataclasses, Pydantic models, typed dicts) for state, model inputs/outputs as commands and responses, not as random param collections, for server-side templates (Django, Jinja) build a view-model in the handler: gather domain data, shape it for display, then pass it to the template, don’t inject ORM objects with lazy queries and behavior into templates, ensure templates don’t run heavy logic or hit the DB, with Dash/Streamlit/Gradio keep callbacks thin: minimal side effects, maximal reuse of pure functions and services, treat callback code as the glue between UI events and domain use-cases, not as the business logic itself, avoid global variables and singletons holding state across requests, use session/user context and explicit storage (cache, DB, files) with clear serialization, design frontend configuration separately: backend base URL, feature flags, modes, don’t hardcode them in templates and code, put them into env/configs, think about UI security: don’t inject unescaped user input, use escaping in templates, protect sensitive actions with CSRF tokens where you have forms/POSTs, never embed secrets into HTML/JS, for PyScript and other Python-in-the-browser approaches clearly divide what can safely live on the client (visualization, local computations, non-sensitive logic) and what must stay on the backend (authorization, sensitive data, DB operations), don’t move critical control to client-side Python, for desktop UIs (PyQt, Tkinter, Kivy, etc.) use MVVM/MVP-style patterns: View renders, Presenter/ViewModel manages state and calls use-cases, the domain knows nothing about widgets, all interaction goes through interfaces/services, encapsulate navigation between windows/screens in its own layer, views don’t spawn arbitrary new windows on their own, encapsulate file, network and DB access in infrastructure services, UI calls service methods instead of using `os`, `requests`, raw SQL directly, build i18n in from the start: put UI texts into resource/translation files, don’t hardcode strings, respect locale formatting for dates/numbers, treat error handling as part of UX: show friendly messages, don’t crash with a traceback in the UI, log errors and unexpected situations separately, test business logic and the application/state layer independently from the UI (unit tests for pure functions and services), cover UI bits (templates, callbacks) with integration/scenario tests where it adds value, watch performance: don’t run heavy computations and big queries on the UI thread (desktop), don’t render unnecessary content or produce huge HTML pages without pagination/lazy-loading, structure the project by architecture and features rather than just “views/templates/utils”, extract `domain`, `app/state`, `infra`, `ui`, avoid code leaking into random modules, keep the Python frontend as a thin layer over the backend and domain with clear layer boundaries, minimal coupling, strong data typing and high testability, so you can change and reuse the UI without rewriting all the logic.
### C lang
начинаем не с указателей, битовых трюков и микрооптимизаций, а с понимания задачи и домена, формулируем, что именно должна делать программа на C, какие данные она обрабатывает, какие инварианты должны всегда сохраняться, разделяем архитектуру на уровни: домен и алгоритмы, абстракции платформы, низкоуровневая инфраструктура (ОС, драйверы, железо), не смешиваем бизнес-логику с системными вызовами и манипуляциями памятью в одних и тех же функциях, проектируем модули как наборы чётких интерфейсов в `.h` и скрытую реализацию в `.c`, экспортируем только то, что действительно нужно, используем `static` и внутренние заголовки, чтобы не выносить детали наружу, даём каждому модулю одну чёткую ответственность, избегаем «god-файлов» с сотнями функций и глобальных переменных, строим зависимости направленными и как можно более слабыми, не делаем циклических включений заголовков, используем forward-declaration там, где возможно, проектируем API функций ясными и простыми: небольшое число параметров, понятные имена, чётко определённые договорённости по ownership памяти и срокам жизни данных, везде явно описываем, кто выделяет память, кто освобождает, где копируется, а где передаётся владение, не прячем управление памятью за «магией», предпочитаем явные `init`/`free` функции и один стиль очистки ресурсов, принимаем, что C — язык ручного управления ресурсами, проектируем паттерны RAII-подобно на уровне стиля: один выход из функции через метку `cleanup:`, где последовательно освобождаются ресурсы, избегаем множества `return` посередине функции без освобождения памяти и дескрипторов, минимизируем использование сырого `malloc/free` по всему коду, инкапсулируем аллокации в функции-обёртки или абстракции (пулы, фабрики, контейнеры), явно договариваемся о формате ошибок: коды возврата, `errno`, структуры ошибок, никогда не игнорируем коды возврата системных вызовов и библиотек, документируем, какие функции могут изменить `errno`, избегаем UB (undefined behavior) сознательно: не читаем неинициализированную память, не выходим за границы массивов, не разыменовываем `NULL` и висячие указатели, соблюдаем правила строгого aliasing, не полагаемся на порядок вычисления выражений, включаем максимум предупреждений компилятора и тритаем их как ошибки, используем статический анализ и санитайзеры там, где возможно, projectируем структуры данных с учётом выравнивания, размерности и кэш-локальности, для горячих участков кода думаем о layout данных (array-of-structs vs struct-of-arrays), но оптимизируем только после измерений, сначала делаем код корректным и читаемым, подробно документируем договорённости в заголовках: что делает функция, какие аргументы допустимы, какие побочные эффекты, как обрабатываются ошибки, какие структуры можно копировать обычным `memcpy`, а какие требуют специальных функций, избегаем макросов везде, где можно обойтись функциями и `inline`, макросы используем только там, где они действительно оправданы (условная компиляция, простые helper’ы без побочных эффектов), не пишем сложную «метапрограммную» магию на препроцессоре, где это сломает отладку и читаемость, следим за границами между платформозависимым и переносимым кодом: всё специфичное к ОС и компилятору прячем в отдельные модули, интерфейс которых чистый и переносимый, проектируем систему сборки (Make/CMake/модульная) как часть архитектуры: одна цель — один модуль/библиотека, зависимости отражают реальные связи, не собираем всё в один огромный объект, минимизируем глобальное состояние: используем `static` и контексты, передаём указатели на состояние явно, избегаем скрытых синглтонов и общих модулей, к которым «подключаются все», проектируем многопоточность осторожно: чётко определяем, какие данные разделяются, защищаем их `mutex`/атомиками, минимизируем shared state, при необходимости используем модели с очередями и сообщениями вместо свободного доступа к общим структурам, логируем ключевые события и ошибки единообразно, не печатаем отладочную информацию «как попало», отделяем отладочный вывод от пользовательского, проектируем тестируемость: пишем чистые функции без глобального состояния, используем интерфейсы на уровне указателей на функции и структур с операциями, чтобы подменять реализацию в тестах, отделяем код, требующий железа/ОС, от чистой логики, структурируем проект по каталогам: `include/` для публичных заголовков, `src/` для реализации, `tests/` для тестов, внутри — по доменным областям, а не по типу файла, следуем одному код-стайлу и форматированию, автоматизируем линтеры и форматтеры, документируем протоколы, бинарные форматы и ABI, помним, что C легко привязывает нас к платформе, поэтому заранее учитываем переносимость: используем стандартную библиотеку, стандартизованные типы (`stdint.h`), избегаем неportable расширений без необходимости, проектируем C-код так, чтобы новый разработчик мог по заголовкам и структуре файлов быстро понять, какие есть модули, какие зависимости допустимы и какие инварианты поддерживаются, и на всех уровнях разработки на C поддерживаем строгие границы модулей, явные контракты, аккуратное управление ресурсами, минимум UB и глобального состояния, высокую тестируемость и эволюционную расширяемость.
start not from pointers, bit tricks and micro-optimizations, but from the problem and domain, define precisely what your C program should do, what data it manipulates, which invariants must always hold, split your architecture into layers: domain and algorithms, platform abstraction, low-level infrastructure (OS, drivers, hardware), don’t mix business logic with syscalls and raw memory juggling in the same functions, design modules as clear interfaces in `.h` and hidden implementations in `.c`, export only what’s truly needed, use `static` and internal headers to keep details private, give each module one focused responsibility, avoid “god files” with hundreds of functions and globals, keep dependencies one-directional and as weak as possible, avoid cyclic header includes, use forward declarations where possible, design function APIs to be clear and simple: small parameter lists, meaningful names, well-defined contracts for memory ownership and lifetimes, always be explicit about who allocates, who frees, where data is copied and where ownership is transferred, don’t hide memory management behind “magic”, prefer explicit `init`/`free` functions and a consistent cleanup style, accept that C is a manual resource management language and design patterns RAII-like at the code style level: a single exit path via a `cleanup:` label where you free resources in reverse order, avoid multiple mid-function `return`s that leak memory or handles, minimize raw `malloc/free` scattered across the code, encapsulate allocations in wrapper functions or abstractions (pools, factories, containers), define error reporting conventions: return codes, `errno`, error structs, never ignore return values of system/library calls, document which functions touch `errno`, consciously avoid undefined behavior: don’t read uninitialized memory, don’t go past array bounds, don’t dereference `NULL` or dangling pointers, respect strict aliasing rules, don’t rely on expression evaluation order, enable maximum compiler warnings and treat them as errors, use static analysis and sanitizers where possible, design data structures with alignment, size and cache locality in mind, for hot paths think about data layout (array-of-structs vs struct-of-arrays), but only optimize after measuring, make correctness and readability first, document contracts in headers: what each function does, valid arguments, side effects, error handling, which structs can be safely `memcpy`’d and which require custom copy functions, avoid macros wherever functions and `inline` can do the job, reserve macros for truly necessary cases (conditional compilation, tiny side-effect-free helpers), don’t write heavy “metaprogramming” macro magic that ruins debugging and readability, maintain clear boundaries between portable code and platform-specific code: hide OS/compiler specifics in dedicated modules with clean, portable interfaces, treat the build system (Make/CMake/other) as part of the architecture: one target per module/library, dependencies that mirror real relationships, not “one giant object file”, minimize global state: use `static` and context structs, pass state pointers explicitly, avoid hidden singletons and shared modules that “everyone includes”, design concurrency carefully: define exactly which data is shared, protect it with mutexes/atomics, minimize shared state, use queues/message-passing patterns when appropriate instead of free-for-all shared structures, log key events and errors consistently, don’t spray debug prints everywhere, separate debug output from user-facing output, design for testability: write pure functions that don’t touch globals, use function pointers / vtables style structs as interfaces so you can swap implementations in tests, separate code that needs real hardware/OS from pure logic, structure the project into directories: `include/` for public headers, `src/` for implementation, `tests/` for tests, and within those organize by domain features instead of file types, follow a single code style and formatting, automate linters and formatters, document protocols, binary formats and ABIs, remember C tends to lock you to a platform so consider portability up front: use the standard library and standardized types (`stdint.h`), avoid non-portable extensions unless absolutely necessary, design C code so a new developer can infer from headers and file layout what modules exist, which dependencies are allowed and which invariants are enforced, and at all levels of C development maintain strict module boundaries, explicit contracts, careful resource management, minimal UB and global state, high testability and evolutionary extensibility.
### C++ lang
начинаем не с наследования, шаблонов и перегрузки операторов, а с понимания домена и требований к системе, формулируем какие задачи решает приложение на C++ (игра, движок, сервис, библиотека), какие ограничения по памяти, латентности и платформам, разделяем архитектуру на слои: домен (правила и модели), application/use-cases (оркестрация, сценарии), infrastructure (ОС, файлы, сеть, графика, БД), presentation/UI (если есть), не смешиваем бизнес-логику с прямыми системными вызовами в одних и тех же классах, проектируем модули с чёткими границами и минимальными API, используем заголовки для контрактов, реализации прячем в `.cpp` и внутренних заголовках, избегаем «божественных» классов и «менеджеров всего на свете», даём каждому классу одну чёткую ответственность (SRP), программируем против абстракций (интерфейсы, чисто виртуальные классы, концепты), а не против конкретных реализаций, предпочитаем композицию наследованию, наследование используем только для настоящего полиморфизма и иерархий «is-a», проектируем зависимости направленными и как можно более слабыми (DIP), отделяем домен от фреймворков, UI и конкретных библиотек, скрываем детали реализации через pImpl, внутренние namespace и приватные заголовки, строим API библиотек и подсистем минимальными, стабильными и независимыми от деталей контейнеров и конкретных типов, осознанно выбираем модель управления памятью: по умолчанию value semantics и владение по значению, там где нужно динамическое владение — `std::unique_ptr`, разделённое владение — только при доказанной необходимости (`std::shared_ptr`), сырые указатели используем как невладеющие, явно документируем сроки жизни и владение, не плодим `new/delete` по коду, оборачиваем ресурсы в RAII-объекты (классы, которые в деструкторе освобождают ресурсы), проектируем стабильные инварианты: объект всегда либо в валидном состоянии, либо вообще не существует, избегаем «полуинициализированных» состояний, обрабатываем ошибки как часть контракта: определяем политику (исключения, `std::expected`, коды возврата) и последовательно ей следуем, не смешиваем несколько подходов без необходимости, не бросаем исключения через границы модулей/библиотек без явного договора, для real-time и игр часто ограничиваем или запрещаем исключения, переключаемся на явные результаты и коды ошибок, используем современный C++ (RAII, `std::vector`, `std::string`, `<chrono>`, `<optional>`, `<variant>`, `<filesystem>` и др.), избегаем «C с классами» и ручного управления массивами там, где стандартная библиотека решает задачу безопаснее и понятнее, относимся к шаблонам и метапрограммированию как к мощному, но опасному инструменту: выносим в шаблоны только то, что действительно нужно обобщать, держим template-магии и SFINAE в изолированных, хорошо задокументированных местах, отдаем предпочтение концептам и простым ограниченным шаблонам, не превращаем код в нечитаемую «template-крипту», проектируем структуры данных и размещение в памяти с учётом производительности (особенно в играх и системном коде): data-oriented дизайн, плотные массивы (`std::vector`) вместо россыпи указателей, минимизация аллокаций на горячих путях, но оптимизируем только после профилирования, строим систему сборки и модульность осознанно: несколько библиотек/таргетов отражают архитектурные границы, заголовки минимальны и не тянут лишних зависимостей, используем forward-declaration, PCH и разумный include-стиль, не кладём всё в один монолитный проект, минимизируем глобальное состояние и одинарные синглтоны: используем контексты, объекты-окружения, явную передачу зависимостей, если нужен синглтон — делаем его простым, контролируемым и легко тестируемым, продумываем многопоточность: выбираем модель (actor, job system, thread pool, task-based), отделяем данные, которые разделяются, защищаем их `std::mutex`, `std::shared_mutex`, `std::atomic`, избегаем тонкой ручной синхронизации без абстракций, минимизируем shared state, отдаём приоритет неизменяемым структурам там, где это возможно, используем современный инструментальный стек: включаем строгие предупреждения компилятора и считаем их ошибками, подключаем statically analyzers и sanitizers (ASan, UBSan, TSan), измеряем производительность профайлерами, не оптимизируем «на глаз», пишем тестируемый код: чистые функции и классы без скрытых глобальных зависимостей, используем интерфейсы и шаблонные параметры для внедрения зависимостей (policy-based design, strategy), отделяем низкоуровневый код от домена, пишем модульные и интеграционные тесты, особенно для сложных алгоритмов, протоколов и критичных подсистем, строим структуру каталогов и пространств имён по доменным областям и слоям, а не по типу файла, называем классы и функции по роли, избегаем абстрактных `Manager`, `Helper`, `Util` без контекста, документируем публичные интерфейсы и важные неочевидные инварианты, не перегружаем код комментариями к тривиальным вещам, выравниваем команды по единым код-стайлам и форматированию, автоматизируем линтеры и форматтеры, различаем portable и платформенно-зависимый код, оборачиваем системно-зависимые части в тонкие абстракции, чтобы можно было портировать игру/движок/систему на другие платформы, проектируем C++-систему так, чтобы по заголовкам и модульной структуре было легко понять, где домен, где инфраструктура, какие зависимости допустимы, а какие нет, и на всех уровнях разработки на C++ поддерживаем строгие границы модулей, явные контракты, контролируемое использование ресурсов, разумную производительность через архитектуру, а не хаотичные микрооптимизации, и высокую тестируемость даже для низкоуровневого кода.
start not from inheritance, templates and operator overloading, but from the problem domain and system requirements, define clearly what your C++ application must do (game, engine, service, library), what constraints you have on memory, latency and platforms, split your architecture into layers: domain (rules and models), application/use-cases (orchestration, scenarios), infrastructure (OS, files, network, graphics, DB), presentation/UI (if any), don’t mix business logic with direct syscalls in the same classes, design modules with clear boundaries and minimal APIs, use headers for contracts and hide implementation in `.cpp` and internal headers, avoid god classes and “manager of everything” types, give each class a single responsibility (SRP), code against abstractions (interfaces, pure virtual bases, concepts) instead of concrete implementations, prefer composition over inheritance, use inheritance only for real polymorphic “is-a” hierarchies, keep dependencies directed and as weak as possible (DIP), isolate your domain from frameworks, UI and specific libraries, hide implementation details with pImpl, internal namespaces and private headers, make library and subsystem APIs minimal, stable and independent of container and concrete-type details, choose memory management strategy consciously: default to value semantics and stack ownership, when you need dynamic ownership use `std::unique_ptr`, use shared ownership only when you can justify it (`std::shared_ptr`), treat raw pointers as non-owning, document lifetimes and ownership explicitly, avoid sprinkling `new/delete` all over the code, wrap resources in RAII objects (classes whose destructors free resources), maintain strong invariants: an object is either valid or doesn’t exist, avoid half-initialized “zombie” states, treat error handling as part of your public contract: decide on a policy (exceptions, `std::expected`-style results, error codes) and stick to it consistently, don’t mix several styles without good reasons, don’t throw exceptions across module/library boundaries without explicit agreement, in real-time and game scenarios often limit or forbid exceptions and switch to explicit results and error codes, lean on modern C++ facilities (RAII, `std::vector`, `std::string`, `<chrono>`, `<optional>`, `<variant>`, `<filesystem>` etc.), avoid writing “C with classes” and managing raw arrays where the standard library can do it more safely and clearly, treat templates and metaprogramming as powerful but sharp tools: template only what truly needs to be generic, keep template magic and SFINAE isolated and well documented, favor concepts and simple constrained templates, don’t turn your code into unreadable template cryptography, design data structures and memory layouts with performance in mind (especially in games and systems): data-oriented design, tight arrays (`std::vector`) instead of pointer forests, minimize allocations on hot paths, but only after profiling, first make code correct and readable, design your build and modularization consciously: multiple libraries/targets reflect architectural boundaries, headers are minimal and don’t pull in unnecessary dependencies, use forward declarations, PCH and sane include discipline, don’t cram everything into one monolithic target, minimize global state and “just one singleton”: use context objects and explicit dependency passing, if you need a singleton make it simple, controlled and testable, think carefully about concurrency: choose a model (actors, job system, thread pool, task-based), clearly identify shared data, protect it with `std::mutex`, `std::shared_mutex`, `std::atomic`, minimize shared mutable state, use message-passing / queues where appropriate instead of free-for-all access, use modern tooling: enable strict compiler warnings and treat them as errors, run static analyzers and sanitizers (ASan, UBSan, TSan) where possible, measure performance with profilers instead of guessing, write testable code: pure functions and classes without hidden global dependencies, use interfaces and template parameters for dependency injection (policy-based design, strategy), separate low-level code from domain logic, write unit and integration tests, especially for complex algorithms, protocols and critical subsystems, organize folders and namespaces by domain and layers rather than file types, give classes and functions role-based names and avoid vague `Manager`, `Helper`, `Util` without context, document public interfaces and important non-obvious invariants, avoid commenting trivialities and instead improve names and structure, agree on a code style and formatting, automate linters and formatters, distinguish portable and platform-specific code, wrap platform-specific parts in thin abstractions so you can port your game/engine/system to other platforms, design your C++ system so that headers and module structure make it obvious where the domain lives, where infrastructure lives, and which dependencies are allowed and which are forbidden, and at every level of C++ development keep strict module boundaries, explicit contracts, well-controlled resource usage, performance achieved through good architecture rather than random hacks, and high testability even for low-level code.
### Rust lang
начинаем не с токио, actix и модных crates, а с понимания домена и требований к системе, формулируем, какие задачи решает приложение на Rust, какие инварианты должны соблюдаться, какие границы у системы и какие у неё нефункциональные требования (латентность, пропускная способность, память, безопасная конкуррентность), разделяем архитектуру на слои: домен (модели и правила), application/use-cases (команды, сценарии, оркестрация), infrastructure (БД, сеть, файловая система, драйверы, FFI, async runtime), presentation/transport (HTTP, CLI, gRPC, UI), рассматриваем Rust не как «язык ради языка», а как средство выразить доменные инварианты через типы, владение и заимствование, моделируем бизнес-правила так, чтобы некорректные состояния было трудно или невозможно представить на уровне типов (newtype, enum, типобезопасные ID, непустые строки, состояния как sum types), используем систему владения и заимствований как инструмент архитектуры: ясно описываем, кто владеет данными, кто только читает, кто временно арендует, минимизируем использование `Arc<Mutex<…>>` «на всякий случай», предпочитаем явные immutable ссылки и передачу владения, держим `unsafe` в маленьких, хорошо обоснованных и задокументированных блоках, оборачиваем его в безопасные абстракции и не выносим наружу сырые указатели и инварианты, которые нельзя проверить, проектируем crates как модули архитектуры: отделяем `domain`/`core` как чистую библиотеку без зависимости от runtime и фреймворков, делаем отдельные crates для `infra` (БД, HTTP-клиенты, драйверы), `api`/`presentation` (web/CLI/gRPC), используем workspaces для организации всего решения, программируем против абстракций: описываем порты и контракты через traits, реализации прячем за адаптерами и конкретными типами, используем generics и trait bounds там, где нужна расширяемость, но не превращаем всё в «generic-вселенную» ради энтузиазма, предпочитаем простые конкретные типы и небольшие traits, разделяем синхронные и асинхронные абстракции (отдельные traits, отдельные адаптеры), не протягиваем `async` и runtime-типы в домен без необходимости, выбираем async runtime (tokio, async-std и др.) как инфраструктурную деталь, а не как часть домена, изолируем использование `tokio::spawn`, `select!`, таймеров и IO в application/infra слоях, контролируем границы задач и владение данными между ними, продумываем Send/Sync и `'static` не как «магические метки», а как реальную модель доступности данных между потоками, не размечаем всё `'static` ради того, чтобы код скомпилировался, используем lifetimes как способ показать, что данные живут ровно столько, сколько нужно, проектируем ошибки как часть публичного контракта: используем `Result<T, E>` везде, где может быть сбой, различаем доменные ошибки и технические, для доменных заводим свои enum’ы, для инфраструктуры — оборачиваем внешние ошибки через `thiserror` или `anyhow` там, где нужен удобный контекст, не используем `panic!` и `unwrap`/`expect` в нормальном рабочем потоке, оставляем их только для явно невозможных ситуаций и тестов, держим код `no_std`/embedded в отдельном слое/крэйте, рассматриваем HAL и конкретные микроконтроллеры как инфраструктуру, домен там тоже должен быть максимально чистым, для FFI явно выделяем границу: пишем тонкий слой `extern "C"`/`#[no_mangle]` поверх безопасного Rust-ядра, не тащим `*mut c_void` в домен, проектируем структуры данных с учётом производительности и владения: по умолчанию используем `Vec`, `String`, `HashMap`/`BTreeMap`/`IndexMap` в домене, для hot-path частей (игровые циклы, рендер, высоконагруженные сервисы) применяем data-oriented подход, арендуемые коллекции, пуллы, но только после измерений, сначала добиваемся корректности и ясности, используем модули и `pub`/`pub(crate)` как способ выразить границы, не превращаем всё в `pub`, чётко отделяем внутренние детали от публичного API, тщательно проектируем API библиотек: минимальные и стабильные типы, отсутствие лишних generic-параметров, понятные конструкторы (builder’ы, фабрики), корректные Send/Sync гарантии, заранее думаем о версионировании crate’ов и эволюции публичных интерфейсов, отделяем конфигурацию от кода: используем типизированные структуры конфигураций, загружаем их из env/файлов/флагов в одном месте, не читаем `std::env::var` из каждого угла, учитываем безопасность: строго относимся к FFI, сериализации/десериализации и парсингу входных данных, используем `serde` и типобезопасные форматы, проверяем границы, не доверяем внешнему миру, логирование и observability проектируем изначально: используем `tracing`/`log` + адаптер, добавляем span’ы и контекст, не швыряем `println!` везде, проектируем тестируемость: делаем доменные функции и типы независимыми от окружения, используем инжекцию зависимостей через generic-параметры и traits вместо глобальных singletons, пишем unit-тесты внутри модулей (`#[cfg(test)]`), интеграционные тесты в `tests/`, по возможности property-based тесты (proptest/quickcheck) для критичных инвариантов, минимизируем «магические» макросы и proc-macros, держим их в отдельных crate’ах и хорошо документируем, не скрываем сложное поведение за одной аннотацией без понятного контракта, строим структуру проекта так, чтобы по модулям и крейтам можно было прочитать архитектуру: где домен, где application, где инфраструктура, где transport, избегаем папок `utils` и `misc`, называем модули по ролям и предметным областям, относимся к «zero-cost abstractions» как к преимуществу, а не как к оправданию сложной магии: сначала простой чистый дизайн, потом при необходимости — точечные оптимизации, измеренные профилятором, проектируем Rust-систему так, чтобы новый разработчик по типам, модулям и trait’ам мог понять инварианты, жизненный цикл данных и допустимые зависимости, и на всех уровнях разработки на Rust поддерживаем строгие границы модулей и crate’ов, явные контракты через типы и traits, контролируемое владение и заимствование вместо «shared mutable хаоса», минимальное и изолированное `unsafe`, высокую тестируемость и производительность, достигаемую архитектурой, а не случайными трюками.
start not from Tokio, actix and shiny crates, but from the domain and system requirements, define what your Rust application is supposed to do, which invariants must always hold, what its boundaries are and what non-functional requirements it has (latency, throughput, memory, safe concurrency), split the architecture into layers: domain (models and rules), application/use-cases (commands, scenarios, orchestration), infrastructure (database, network, filesystem, drivers, FFI, async runtime), presentation/transport (HTTP, CLI, gRPC, UI), treat Rust not as “a language for its own sake” but as a way to encode domain invariants in types, ownership and borrowing, model business rules so that invalid states are hard or impossible to express at the type level (newtypes, enums, type-safe IDs, non-empty strings, state machines as sum types), use the ownership and borrowing system as an architectural tool: be explicit about who owns data, who only reads, who borrows temporarily, minimize `Arc<Mutex<…>>` sprinkled “just in case”, prefer clear immutable references and moves of ownership, keep `unsafe` confined to tiny, well-justified and well-documented blocks, wrap it into safe abstractions and never leak raw pointers or unverifiable invariants into the rest of the code, design crates as architectural modules: separate a `domain`/`core` crate as a pure library with no runtime or framework dependency, create dedicated crates for `infra` (DB, HTTP clients, drivers), `api`/`presentation` (web/CLI/gRPC), organize the whole system as a Cargo workspace, code against abstractions: define ports and contracts via traits, hide implementations behind adapters and concrete types, use generics and trait bounds where extensibility is truly needed, but don’t turn everything into “generic-everything” for fun, prefer simple concrete types and small, focused traits, separate sync and async abstractions (distinct traits, distinct adapters), don’t drag `async` and runtime types into the domain unless absolutely necessary, choose an async runtime (Tokio, async-std, etc.) as an infrastructure detail, not part of the domain, isolate `tokio::spawn`, `select!`, timers and IO in the application/infra layers, reason carefully about task boundaries and data ownership between tasks, treat `Send`/`Sync` and `'static` not as “magic annotations” but as your true model of cross-thread accessibility, don’t slap `'static` on everything just to make it compile, use lifetimes to express that data lives exactly as long as it must, design errors as part of the public contract: use `Result<T, E>` everywhere failure is possible, distinguish domain errors from technical ones, for domain errors define your own enums, for infra errors wrap external errors with context via `thiserror` or `anyhow` where ergonomic, avoid `panic!` and `unwrap`/`expect` in normal code paths, reserve them for truly impossible conditions and tests, keep `no_std`/embedded code in its own layer/crate, treat HALs and concrete MCUs as infrastructure, keep the domain there as clean as possible too, define explicit FFI boundaries: write a thin `extern "C"`/`#[no_mangle]` layer on top of safe Rust core logic, don’t let `*mut c_void` seep into domain code, design data structures with performance and ownership in mind: by default use `Vec`, `String`, `HashMap`/`BTreeMap`/`IndexMap` in the domain, for hot paths (game loops, rendering, high-load services) move to data-oriented layouts, rented collections, pools, but only after measurement, first make the code correct and clear, use modules and `pub`/`pub(crate)` to express boundaries, don’t mark everything `pub`, separate internal details from public API, carefully design library APIs: minimal and stable types, no unnecessary generic parameters, clear constructors (builders, factories), correct `Send`/`Sync` guarantees, think ahead about crate versioning and how public interfaces evolve, separate configuration from code: use strongly typed config structs, load them from env/files/flags in one place, don’t call `std::env::var` from random locations, take security seriously: be strict with FFI, serialization/deserialization and parsing, rely on `serde` and type-safe formats, check bounds, never trust external input, design logging and observability from the start: use `tracing`/`log` + a backend, add spans and context, don’t litter code with `println!`, design for testability: keep domain functions and types independent of the environment, use dependency injection via generics and traits instead of global singletons, write unit tests inside modules (`#[cfg(test)]`), integration tests in `tests/`, use property-based tests (proptest/quickcheck) where invariants are critical, minimize “magic” macros and proc-macros, keep them in dedicated crates and document them well, don’t hide complex behavior behind a single attribute without a clear contract, structure the project so that the module and crate layout reflects the architecture: where domain lives, where application logic lives, where infra/transport lives, avoid `utils`/`misc` dumping grounds, name modules by role and domain area, treat “zero-cost abstractions” as an advantage, not as an excuse for unnecessary cleverness: start with simple, clean design, then apply profiled, targeted optimizations, design the Rust system so that a new engineer can infer invariants, data lifecycles and allowed dependencies from types, modules and traits alone, and at every level of Rust development maintain strict boundaries between modules and crates, explicit contracts via types and traits, controlled ownership and borrowing instead of shared mutable chaos, minimal and isolated `unsafe`, high testability and performance that comes from architecture, not random hacks.
### C# lang
начинаем не с выбора фреймворка .NET, не с ASP.NET Core, Blazor или Unity, а с понимания домена и задач системы, формулируем бизнес-цели и ключевые сценарии, разделяем архитектуру на слои: домен (модели и правила), application/use-cases (команды, оркестрация), infrastructure (БД, файлы, сеть, внешние API), presentation (web, desktop, mobile, игры), не смешиваем бизнес-логику с UI, HTTP и EF Core в одних и тех же классах, строим домен так, чтобы он не зависел от фреймворка, ASP.NET, WPF, Unity и конкретных библиотек, считаем DbContext, HttpContext, Controller, MonoBehaviour инфраструктурой на границе системы, явно разделяем domain-модели, DTO для транспортного слоя и ViewModel’и для UI, не таскаем EF-сущности прямо в JSON и XAML, используем SOLID осмысленно: один класс — одна ответственность, расширяем поведение через новые реализации, а не переписывание стабильного кода, интерфейсы маленькие и целевые, высокоуровневый код зависит от абстракций, а не от деталей, программируем против интерфейсов (IRepository, IClock, INotificationService), а не против конкретных классов, внедряем зависимости через конструктор и DI-контейнер, а не создаём new везде и не используем Service Locator, держим composition root в одном месте (например, Program/Startup), а внутри слоёв не знаем про конкретный контейнер, осмысленно используем async/await: не блокируем Task.Result и Wait, в публичных API почти всегда возвращаем Task/Task<T>, избегаем async void, прокидываем CancellationToken до инфраструктуры, не забываем обрабатывать отмену, в web/desktop/играх разделяем CPU-bound и IO-bound операции, асинхронность не превращаем в хаос из ConfigureAwait(false) без понимания, относимся к EF Core как к инфраструктурному слою: DbContext — внутренняя деталь репозиториев и unit of work, домен видит только интерфейсы и агрегаты, не строим сложный LINQ прямо в контроллерах, не отдаём наружу IQueryable как контракт, следим за жизненным циклом контекста и кешированием, проектируем модели с учётом nullable reference types: включаем #nullable enable, явно размечаем, что может быть null, уменьшаем количество NullReferenceException контрактом типов, используем record/immutable типы там, где важно неизменяемое состояние, осознанно выбираем между class и struct: value-типы только для маленьких, часто копируемых и простых значений, не делаем огромных struct’ов ради микрооптимизаций, проектируем обработку ошибок как часть контракта: в домене используем свои исключения или Result-типы, в инфраструктуре аккуратно оборачиваем внешние исключения, не глушим catch (Exception) без логирования и реакции, в ASP.NET Core маппим исключения на корректные HTTP-коды в middleware, применяем ILogger и структурированное логирование, не ставим везде Console.WriteLine, выносим конфигурацию в appsettings, переменные окружения и секрет-хранилища, используем IOptions и сильные типы конфигурации, не хардкодим строки подключения, ключи и URL, думаем о тестируемости: отделяем домен от фреймворка, пишем юнит-тесты на чистые сервисы и use-cases без ASP.NET/WPF/EF, используем in-memory/тестовые реализации интерфейсов, интеграционными тестами проверяем контроллеры, middleware и репозитории, проектируем namespace’ы и структуру проектов по архитектуре и домену, а не по типу файла, выделяем проекты *.Domain, *.Application, *.Infrastructure, *.Web/*.UI, избегаем папок Helpers и Utils без смысла, везде даём говорящие имена (OrderService — про заказы, а не «делает всё»), следим за управлением ресурсами: используем using/await using и IDisposable/IAsyncDisposable, не допускаем утечек сокетов, потоков и файлов, продумываем многопоточность и Task-параллелизм: защищаем общий mutable-state через lock/конкурентные коллекции, минимизируем static-глобалы, используем Immutable* коллекции там, где важно, осознаём, что async — это не новый поток, а кооперативная многозадачность, для высоконагруженных сервисов учим пайплайн ASP.NET Core, ограничения по памяти и времени, регулярно профилируем, а не оптимизируем на глаз, документируем ключевые решения (ADRs): почему выбрали такой DI, ORM, шаблон архитектуры, как устроены слои и границы, следуем единому код-стайлу (analyzers, форматтер), используем Roslyn-аналитики и nullable, treat warnings as errors, проектируем C#-приложения так, чтобы новый разработчик, открыв solution, по проектам и интерфейсам сразу понял, где домен, где инфраструктура, как протекают зависимости, и на всех уровнях поддерживаем: строгие границы слоёв, явные контракты через интерфейсы и типы, минимальную магию фреймворка, чёткое управление асинхронностью, тестируемость и эволюционную расширяемость без переписывания с нуля.
start not from choosing a framework (.NET, ASP.NET Core, Blazor, Unity) but from the domain and system goals, define what the app actually does, which business scenarios it supports and what non-functional requirements you have, split the architecture into layers: domain (models and rules), application/use-cases (commands, orchestration), infrastructure (DB, files, network, external APIs), presentation (web, desktop, mobile, games), don’t mix business logic with UI, HTTP and EF Core in the same classes, make the domain independent from ASP.NET, WPF, Unity and specific libraries, treat DbContext, HttpContext, Controller, MonoBehaviour as infrastructure at the edges, clearly separate domain models, transport DTOs and UI view models, don’t dump EF entities straight into JSON or XAML, use SOLID deliberately: one class — one reason to change, extend behavior via new implementations instead of rewriting stable code, keep interfaces small and focused, let high-level code depend on abstractions, not details, code against interfaces (IRepository, IClock, INotificationService) instead of concrete types, inject dependencies via constructors and a DI container, not by sprinkling new everywhere or using a Service Locator, keep the composition root in one place (Program/Startup), and keep the inner layers unaware of the container, use async/await thoughtfully: don’t block on Task.Result or Wait, return Task/Task<T> from async APIs, avoid async void except for event handlers, pass CancellationToken down to infra and actually honor cancellation, separate CPU-bound from IO-bound work, don’t turn async into chaos of random ConfigureAwait(false), treat EF Core as infrastructure: DbContext is an internal detail of repositories/unit-of-work, the domain sees only interfaces and aggregates, don’t build huge LINQ queries inside controllers, don’t expose IQueryable as a contract, be careful with context lifetime and caching, design models with nullable reference types in mind: turn #nullable enable on, be explicit about what can be null, reduce NullReferenceException by using the type system, use record and immutable types where stable state matters, choose between class and struct consciously: value types only for small, simple values where copy semantics make sense, not giant structs for micro-optimizations, treat error handling as part of the contract: in the domain use your own exceptions or Result types, in infra wrap external exceptions with context, don’t swallow catch (Exception) without logging and reaction, in ASP.NET Core map exceptions to HTTP status codes via middleware, use ILogger and structured logging, not just scattered Console.WriteLine, move configuration into appsettings, environment variables and secret stores, use IOptions and strongly typed config, don’t hardcode connection strings, keys and URLs, think about testability: separate domain from framework, write unit tests for pure services and use-cases without ASP.NET/WPF/EF, use in-memory/test implementations for interfaces, use integration tests for controllers, middleware and repositories, design namespaces and projects by architecture and domain, not just file type — separate *.Domain, *.Application, *.Infrastructure, *.Web/*.UI, avoid Helpers/Utils dumping grounds, name things by role, manage resources properly with using/await using and IDisposable/IAsyncDisposable, don’t leak sockets, threads or files, design concurrency and Tasks carefully: protect shared mutable state with lock/concurrent collections, minimize static globals, use Immutable* collections where appropriate, understand that async is cooperative scheduling, not a new thread, for high-load services understand the ASP.NET Core pipeline, memory and time limits, profile regularly instead of guessing, document key choices (ADRs): why you picked a DI container, ORM, architecture style, what the boundaries are, enforce a consistent code style (analyzers, formatters), use Roslyn analyzers and nullable, treat warnings as errors, design C# applications so a new developer can open the solution and, from projects and interfaces, immediately see where the domain is, where infrastructure is, and how dependencies flow, and at every level keep strict layer boundaries, explicit contracts via interfaces and types, minimal framework magic, clear async handling, testability and evolutionary change without constant rewrites.
### Codapt Trysolid React 18, TypeScript, Tailwind CSS, Vite, Vitest, React Testing Library, Node.js, tRPC, TypeScript (backend), SQLite, PostgreSQL, REST API (опционально), Prisma, Docker, Nginx, ESLint, Prettier
start with deep understanding of the domain, stakeholders, and measurable business goals, gather, refine, and document functional and non-functional requirements together with the business, build a shared glossary and ubiquitous language and reflect it in TypeScript types, tRPC contracts, database schema, and React copy, explicitly describe system boundaries, bounded contexts, and interaction patterns between them, design the architecture and APIs from the domain and use cases rather than from React, Node.js, or Prisma frameworks, separate business logic and infrastructure at both conceptual and code levels, build the backend around application / use-case layers that orchestrate domain logic, avoid direct dependencies from the domain to React, databases, network, or filesystem, first define the domain model, entities, value objects, and their invariants before choosing storage or transport, distinguish entities and value objects by identity and mutability and give each clear responsibilities, define aggregates and their boundaries to control consistency and transactional invariants, model domain events and their handlers for significant business facts, define repository and domain service interfaces as ports independent of Prisma, SQLite, or PostgreSQL details, apply ports-and-adapters (hexagonal) architecture so tRPC / REST controllers, Prisma repositories, and other adapters implement domain ports, keep each module, class, and function with one clear responsibility (SRP), extend behavior by adding new implementations and wiring instead of modifying stable domain code (OCP), respect LSP so substitutable implementations do not break client expectations, split fat interfaces into small focused ones (ISP), let high-level policies depend on abstractions rather than concrete adapters, frameworks, or libraries (DIP), explicitly declare interfaces for all external dependencies and inject them via constructors, factories, or a DI container instead of using service locators or hidden singletons, design modules with minimal, explicit, and understandable APIs and hide implementation details, keep dependencies between modules one-directional and as weak as possible, prefer composition over inheritance and use inheritance only for true polymorphic hierarchies, avoid god objects and vague “manager of everything” classes, keep modules and files small and cohesive and decompose complex classes into simpler ones, explicitly separate layers: domain, application, infrastructure, presentation (React 18), do not allow dependencies from inner layers to outer layers, let React components depend on the application layer (tRPC / REST clients) rather than directly on domain or database, let infrastructure (Prisma repositories, Nginx, file system, SMTP, queues) implement domain ports instead of dictating domain shape, keep all configuration (URLs, keys, feature flags) out of code and in environment variables, config files, secret stores, Docker and Nginx config, never hardcode secrets or credentials in the repository, do not mix business logic with I/O operations, keep network, file, and database access concentrated in adapters and infrastructure, keep as many functions as possible pure and deterministic, separate read and write operations where it brings value (CQRS-style), use optimized simple models for reads and strict domain models for writes, use simple DTOs to transfer data between layers and do not drag domain entities into React components, transport, or persistence, design API contracts (tRPC routers, optional REST endpoints) to be explicit, stable, and backward compatible, version externally exposed APIs and events, clearly describe message schemas and data formats, handle errors and exceptions explicitly and design error types and HTTP response codes as part of the contract, distinguish business errors from technical failures, never swallow exceptions without logging and appropriate handling, validate data both at system boundaries (React forms, tRPC / REST inputs) and inside the domain, let domain invariants be enforced by the domain itself, not only by the UI, design security from the beginning: authentication, authorization, audit, and data privacy, minimize the attack surface and avoid exposing unnecessary details or internal structure in APIs and errors, log key events at system boundaries and in critical domain points using structured logging, add metrics and tracing for observability and design the system so it can be monitored and alerted in production, choose communication protocols and formats (tRPC, REST, WebSocket, events) based on requirements, not fashion, design the system with network errors, latency, and temporary service unavailability in mind and apply retries, timeouts, and circuit breakers where appropriate, think about idempotency of operations that may be retried, ensure correct data migration and schema evolution for SQLite and PostgreSQL, plan database migration and rollback strategies with Prisma and keep them backward compatible, use feature flags to enable new capabilities safely and to roll back quickly, design code so that it can be rolled out incrementally via Docker and Nginx and rolled back safely, think about scaling independently along the read axis and the write axis, start with a simple, well-structured monolith and split into services only when there is a real need, when using microservices, clearly define bounded contexts and data ownership and minimize synchronous inter-service dependencies, use asynchronous communication and events when justified, avoid distributed transactions and compensating operations unless absolutely necessary and well understood, design conventions for idempotency and data consistency across services and messages, think ahead about schema evolution for messages and contracts, write code so that it is easy to test and separate business logic from frameworks (React, Vite, tRPC, Prisma), cover the domain with fast unit tests using Vitest, test UI behavior with React Testing Library, test integration at the level of adapters and boundaries, use test doubles for external dependencies and automate key end-to-end scenarios, keep the test suite fast, reliable, and integrated into the CI/CD pipeline, design folder and module structure to reflect the architecture and bounded contexts rather than accidental technical groupings, name packages and namespaces according to roles and layers and prefer meaningful domain names, avoid abstract names like Manager, Helper, Utils without clear context, document public contracts (APIs, events) and key business logic, avoid excessive comments on obvious code and improve structure and naming instead of relying on comments, treat code review as a mandatory step and discuss architectural decisions and trade-offs at the team level, record architectural decisions in short ADRs, regularly revisit the architecture in light of new requirements but avoid full rewrites without strong reasons, distinguish temporary solutions from long-term ones and mark technical debt with a plan to pay it off, follow a consistent code style and formatting enforced by ESLint and Prettier on both frontend and backend, do not optimize prematurely: first make the code clear and correct, optimize only after measurement, use profiling to find bottlenecks, avoid “magic” and overly clever solutions in favor of simplicity and explicitness, deliberately limit the technology stack to React 18, TypeScript, Tailwind CSS, Vite, Node.js, tRPC, Prisma, SQLite / PostgreSQL, Docker, Nginx and only add new tools when justified, minimize single points of failure and design the system so individual components can be deployed, updated, and replaced independently, ensure backward compatibility when evolving protocols, models, and database schemas, make the system resilient to partial failures and design graceful degradation and fallback scenarios, think about operability from the start: logging, monitoring, alerts, dashboards, health checks, automate build, tests, deployment, and migrations, design the architecture so that new developers can onboard easily and keep documentation up to date at a useful level, simplify the architecture whenever you can instead of complicating it, continuously balance between ideal cleanliness and pragmatism and wherever possible maintain strict boundaries, explicit contracts, and clear separation of responsibilities between layers, modules, and teams.
### SQL to YAML gpt5.1 ext thinking edition
### SQL → YAML OpenAPI Generator через DDD
начинаем не с таблиц и auto-CRUD, а с домена: какие bounded context’ы есть в системе, какие агрегаты и use-case’ы должны быть у API, формулируем во *внутренней* модели: какие команды и запросы над доменными объектами нужны, а уже потом решаем, какие части SQL-схемы можно безопасно использовать для генерации OpenAPI, не считаем SQL схемой истины для API, БД — это деталь инфраструктуры, генератор должен уметь оторвать публичные контракты от внутренней структуры таблиц, используем DDD-слой/описание (bounded context + aggregate + value objects) как основной вход, а SQL только как источник типов/ограничений, вводим явный mapping-слой: какая таблица/представление соответствует какому ресурсy/DTO в API, какие колонки скрыты, какие переименованы, какие агрегируются, не генерируем эндпоинты «по таблицам» 1:1, сначала проектируем ресурсно-ориентированные API: `customers`, `orders`, `invoices`, а join-таблицы, технические таблицы, event-лог и прочие детали скрываем за моделями, для каждой доменной операции явно решаем: это команда (изменяет состояние) или запрос (только чтение), от этого зависит HTTP-метод и форма контракта, избегаем автоматического «CRUD из таблицы», когда домен диктует более грубые/инвариант-сохраняющие команды (`/orders/{id}/pay`, `/orders/{id}/cancel`), используем метаданные SQL-схемы осмысленно: `NOT NULL` → `required` поля в OpenAPI, `CHECK`/enum-таблицы → ограниченные значения, длина и precision → ограничения схемы, но не тащим в контракт низкоуровневые детали вроде storage-типа или имени индекса, храним в БД расшифровки и комментарии к колонкам/таблицам и используем их как источники описаний (`description`) в OpenAPI, следим, чтобы текст описаний говорился на «вездесущем языке» домена, а не на жаргоне БД, продумываем стратегию именования: имена таблиц/колонок могут быть уродливыми и историческими, генератор обязан уметь маппить их в чистый и последовательный JSON (camelCase, нормальные названия ресурсов, согласованные `id`-поля), делим спецификацию по bounded context’ам (отдельные YAML-файлы/теги), не делаем один гигантский openapi.yaml, в котором всё смешано, проектируем представления для чтения отдельно от моделей записи: один агрегат может иметь несколько DTO (summary, detailed, list-item), генератор должен поддерживать это, а не пытаться выжать один «универсальный» объект из таблицы, используем SQL-представления/материализованные view как источник read-моделей, но не раскрываем их внутреннюю структуру в API, строим ресурсы вокруг смыслов (OrderSummary, CustomerProfile), а не вокруг join’ов, учитываем, что внешние ключи в SQL ≠ то, как мы хотим показывать связи в API: многие связи лучше раскрывать как вложенные объекты/коллекции или ссылки (`links`), а не как голые `*_id`, генератор должен позволять настраивать формат отношений: embedded, ссылки или отдельные ресурсы, сразу планируем стандартные кросс-ресурсные вещи: пагинация, фильтрация, сортировка — задаём единый контракт (например, `page`, `pageSize`, `sort`, `filter`) и учим генератор применять его последовательно к списковым операциям, а не генерировать каждый раз новые параметры, дизайн ошибок не оставляем на «дефолт 500»: строим доменную модель ошибок (валидация, нарушенный инвариант, конфликт, отсутствующий ресурс), задаём единый формат тела ошибки и маппинг SQL-ошибок (уникальные ключи, внешние ключи, check-нарушения) в эти доменные ошибки, генератор не должен выдавать наружу коды/сообщения конкретной СУБД, продумываем безопасность до генерации: какие таблицы/колонки вообще не могут оказаться в OpenAPI (секреты, внутренние тех-флаги), какие данные доступны только определённым ролям/клиентам, закладываем в генератор поддержку `securitySchemes`, `scopes` и пометок на уровне операций/ресурсов, обеспечиваем возможность «отрезать» части схемы БД от экспонирования, учитываем мульти-арендность: если в SQL всё завязано на `tenant_id`, генератор не обязан лепить этот параметр в каждый JSON-объект, вместо этого проектируем явные контракты (`/tenants/{tenantId}/…`, правила фильтрации) и соответствующим образом маппим SQL, делаем миграции и эволюцию схемы частью дизайна генератора: OpenAPI должен уметь жить при изменениях БД, добавление колонок → новые опциональные поля, перенос значений → новые DTO/ресурсы, при этом старые схемы могут оставаться в `deprecated` до окончания переходного периода, не позволяем генератору ломать ранее опубликованные контракты без явного решения, проектируем расширяемость: на каждый «авто»-шаг должна быть возможность переопределения руками — rename полей, переопределение типов, объединение/разделение таблиц в один/несколько ресурсов, аннотации доменной модели должны побеждать «голую» SQL-схему, заранее думаем о тестировании: на каждую сгенерённую спецификацию должны быть контракт-тесты (consumer-driven, генерация mock’ов, валидация ответов по схемам) и sanity-чек, что OpenAPI согласован с актуальной схемой БД, строим генератор так, чтобы он помогал сохранять DDD-границы (контексты, агрегаты, язык) и автоматизировал рутину (типы, валидация, описания), но не подменял собой архитектурные решения: где границы сервисов, какие сценарии вообще стоит выносить в API, какие — нет, и на всех уровнях SQL → доменная модель → OpenAPI следим, чтобы наружу выходили аккуратные, стабильные и понятные контракты, а не случайный снимок структуры таблиц «как получилось».
---
### SQL → YAML OpenAPI Generator via DDD (English)
start not from tables and auto-CRUD but from the domain: identify bounded contexts, aggregates and use cases your API must support, define in the *domain* model which commands and queries exist, and only then decide which parts of the SQL schema can be safely used to generate OpenAPI, don’t treat the DB schema as the source of truth for the API — the DB is an infrastructure detail, the generator should decouple public contracts from internal table layouts, use the DDD layer (bounded contexts + aggregates + value objects) as the primary input and SQL only as a source of types and constraints, introduce an explicit mapping layer: which table/view maps to which API resource/DTO, which columns are hidden, renamed, aggregated, don’t generate endpoints 1:1 per table, design resource-oriented APIs first (`customers`, `orders`, `invoices`) and hide join tables, technical tables, event logs and other plumbing behind proper models, for each domain operation decide whether it’s a command (changes state) or a query (read-only), then choose HTTP verbs and payload shapes accordingly, avoid naïve table-CRUD when the domain wants coarser, invariant-preserving commands (`/orders/{id}/pay`, `/orders/{id}/cancel`), use SQL schema metadata deliberately: `NOT NULL` → `required` fields in OpenAPI, `CHECK`/enum tables → allowed values, length/precision → validation limits, but don’t leak low-level details like storage types or index names into the contract, store human-readable comments/annotations on tables and columns and use them as descriptions in OpenAPI, keeping the text aligned with the ubiquitous language of the domain rather than DB jargon, design a naming strategy: table/column names in SQL can be messy and historical, the generator must map them to clean and consistent JSON (camelCase, sane resource names, consistent `id` fields), split the spec along bounded contexts (separate YAML files/tags), avoid one giant openapi.yaml where everything is mixed, design read models separately from write models: a single aggregate may have multiple DTOs (summary, details, list item), the generator should support that instead of trying to squeeze one “universal” object out of a table, use SQL views/materialized views as sources for read models but don’t expose their internal structure directly — build resources around meanings (OrderSummary, CustomerProfile) rather than joins, remember that foreign keys in SQL ≠ how you want to represent relationships in the API: many relationships are better expressed as embedded objects/collections or links, not just raw `*_id` fields, and the generator must let you configure relationship representation (embedded, referenced, separate resources), standardize cross-resource concerns like pagination, filtering and sorting: define a consistent contract (`page`, `pageSize`, `sort`, `filter`) and teach the generator to apply it uniformly to list operations, don’t let it invent different conventions per table, design the error model intentionally: build a domain-level error taxonomy (validation error, invariant violation, conflict, not found), define a shared error schema and map DB errors (unique constraint, foreign key, check violations) to those domain errors, the generator must not expose raw DB error codes/messages, think about security before generation: which tables/columns must *never* appear in OpenAPI (secrets, internal flags), which data is only available to certain roles/clients, bake in support for `securitySchemes`, scopes and per-operation security, and make it possible to “cut off” parts of the DB schema from exposure, handle multi-tenancy thoughtfully: if SQL uses `tenant_id` everywhere, the generator shouldn’t blindly put that field into every JSON object, instead design explicit contracts (`/tenants/{tenantId}/…`, server-side scoping rules) and map SQL accordingly, treat schema evolution and migrations as part of the generator design: OpenAPI has to survive DB changes, adding columns → new optional fields, moving data → new DTOs/resources, old schemas linger as `deprecated` until consumers migrate, don’t let the generator silently break previously published contracts, design for override and extension: every “auto” step must be overridable — rename fields, override types, merge/split tables into resources, domain annotations should win over raw SQL structure, plan for testing: every generated spec should be validated by contract tests (consumer-driven, schema-based response validation, mocks from OpenAPI) and sanity-checked against the actual DB schema, build the generator so it *preserves* DDD boundaries (contexts, aggregates, ubiquitous language) and automates the boring parts (types, validation, descriptions) without making architectural decisions for you (service boundaries, which scenarios deserve an API), and across the whole SQL → domain model → OpenAPI pipeline ensure that what goes public are clean, stable and understandable contracts, not an accidental snapshot of how your tables happen to look right now.
### sql->yaml claude sonnet 4.5 edition
### SQL to YAML OpenAPI Generator via DDD
начинаем не с парсинга SQL и генерации YAML, а с понимания **домена и бизнес-целей генератора**, формулируем: какие задачи решает инструмент (ускорение документирования API, синхронизация схемы БД и OpenAPI, автоматизация контрактов), для кого он (backend-разработчики, архитекторы, DevOps, команды API-first), какие источники данных (DDL, информационная схема БД, ORM-миграции, существующие OpenAPI-спеки), какие выходные артефакты и формат (OpenAPI 3.0/3.1, Swagger 2.0, JSON/YAML), разделяем **архитектурные слои** чётко: domain (концептуальная модель: Table, Column, Constraint, Relationship, Resource, Operation, Schema, Response), application/use-cases (сценарии: ParseDDL, InferRESTEndpoints, GenerateOpenAPISpec, ValidateMapping, EnrichWithBusinessRules), infrastructure (SQL-парсеры, драйверы БД, YAML-сериализаторы, файловая система, шаблонизаторы), presentation (CLI, web-интерфейс, плагины для IDE, CI/CD интеграция), строим **доменную модель** как ядро: Database (коллекция таблиц и связей), Table (имя, столбцы, ключи, индексы, метаданные), Column (имя, тип SQL, nullable, default, constraints), ForeignKey (source, target, cascade), Index, Constraint, APIResource (endpoint path, HTTP methods, request/response schemas), Operation (GET/POST/PUT/DELETE, параметры, тело запроса, коды ответов), Schema (OpenAPI schema object), Component (reusable schemas, parameters, responses), не смешиваем парсинг SQL и генерацию YAML в одном классе, отделяем чтение схемы БД от вывода OpenAPI, проектируем **bounded contexts**: SQL Schema Context (понимание структуры БД), API Design Context (REST-ресурсы, операции, контракты), OpenAPI Generation Context (сборка спецификации, форматирование, валидация), держим явные границы и переводы между контекстами через антикоррупционные слои, используем **ubiquitous language**: Table → Resource, Column → Property/Field, PrimaryKey → identifier, ForeignKey → relationship/reference, NOT NULL → required, типы SQL маппим в JSON Schema types осознанно (VARCHAR → string, INT → integer, TIMESTAMP → string format date-time), все термины отражаем в коде, комментариях и документации единообразно, проектируем **use-cases как отдельные сервисы/команды**: ParseSQLSchema (читает DDL или подключается к БД, строит доменную модель Database), InferRESTResources (превращает таблицы в REST-ресурсы по конвенции или конфигу), MapColumnsToSchemas (создаёт OpenAPI schema definitions из столбцов таблиц), GenerateOperations (для каждого ресурса генерирует CRUD операции с параметрами и телами), EnrichWithMetadata (добавляет descriptions, examples, tags из аннотаций или внешних источников), ValidateOpenAPISpec (проверяет корректность сгенерированной спеки), SerializeToYAML (сериализует доменную модель OpenAPI в YAML-файл), каждый use-case имеет один чёткий вход (command/query) и выход (result/event), разделяем **инфраструктуру**: SQLParser (адаптер над SQL-парсером: ANTLR, sqlparse, JSqlParser), DatabaseIntrospector (подключается к живой БД через JDBC/psycopg/mysql-connector и читает информационную схему), YAMLSerializer (превращает доменную модель OpenAPI в YAML через библиотеку: PyYAML, js-yaml, SnakeYAML), TemplateEngine (если используем шаблоны для генерации частей спеки), FileSystemAdapter (чтение/запись файлов), ConfigLoader (читает конфиг генератора: mapping rules, naming conventions, tags), все адаптеры реализуют интерфейсы, определённые в домене, а не диктуют ему форму, программируем **против абстракций**: domain зависит от ISQLSchemaReader, IOpenAPIWriter, ITemplateRenderer, а не от конкретных библиотек, внедряем зависимости через конструкторы/DI, избегаем глобальных синглтонов и статических вызовов парсеров, проектируем **маппинг SQL → OpenAPI как набор стратегий и правил**: TableToResourceMapper (конвенция: таблица users → ресурс /users), ColumnToPropertyMapper (типы, форматы, constraints → JSON Schema), RelationshipToLinkMapper (foreign keys → links или nested schemas), используем паттерн Strategy/Visitor для обхода доменной модели и применения правил, закладываем **extensibility points**: пользователь может задать custom mapping rules (через DSL, конфиг, плагины), переопределить naming conventions (snake_case → camelCase, singular → plural), добавить метаданные (описания, примеры, deprecated-флаги) через комментарии в SQL или отдельный файл, проектируем **валидацию на всех этапах**: валидируем входной SQL (синтаксис, семантика), валидируем доменную модель (все таблицы имеют PK, нет циклических зависимостей без ON DELETE CASCADE, все FK ссылаются на существующие таблицы), валидируем сгенерированную OpenAPI-спеку против JSON Schema OpenAPI 3.x, выводим понятные ошибки с указанием места проблемы, разделяем **read-модели и write-модели**: ParsedSchema (чистое представление структуры БД), OpenAPISpecification (чистое представление OpenAPI-документа), intermediate models для трансформаций, не мутируем shared state, используем immutable структуры там, где возможно, проектируем **обработку ошибок** как часть контракта: ParseError (ошибка парсинга SQL), MappingError (не удалось смаппить тип, отсутствует PK), ValidationError (невалидная OpenAPI-спека), SerializationError (ошибка записи YAML), различаем recoverable и non-recoverable ошибки, логируем детали для отладки, выводим пользователю понятные сообщения, закладываем **конфигурируемость и правила**: yaml-конфиг или JSON-файл с mapping rules, naming conventions, игнорируемыми таблицами, кастомными descriptions, tags, security schemes, поддерживаем профили конфигурации (REST-style, GraphQL-style, RPC-style), не хардкодим все правила в коде, делаем инструмент **идемпотентным**: запуск генератора дважды с теми же входными данными даёт тот же результат, не накапливаем мусор, не изменяем существующие файлы непредсказуемо, поддерживаем **инкрементальную генерацию**: если схема БД обновилась (добавлена таблица, колонка), умеем мержить с существующей OpenAPI-спекой, сохраняя ручные правки (descriptions, examples), используем комментарии/аннотации в YAML для маркировки сгенерированных секций, продумываем **observability**: логируем этапы (parsing → mapping → validation → serialization), выводим summary (сколько таблиц обработано, сколько endpoints сгенерировано, сколько ошибок), поддерживаем dry-run режим (показать что будет сгенерировано без записи), проектируем **CLI и API**: CLI-интерфейс с флагами (input SQL file/DB connection string, output YAML path, config path, verbosity, format), programmatic API для интеграции в CI/CD (функция generateOpenAPI(source, config) → spec), плагины для популярных ORM (Prisma, TypeORM, Alembic, Django migrations), строим **структуру проекта по слоям**: domain/ (модели Database, Table, Column, OpenAPISpec, Operation), application/ (use-cases: ParseSchema, GenerateSpec, Validate), infrastructure/ (parsers, serializers, db-connectors, file-io), cli/ (CLI entry point, argument parsing), config/ (default config, schema validation), tests/ (unit-тесты на domain и use-cases, интеграционные на полный pipeline), избегаем папок utils и helpers без контекста, используем **типизацию и контракты**: строгие типы для всех доменных моделей (TypeScript interfaces, Python dataclasses/Pydantic, Java records), валидируем конфиг через JSON Schema, генерируем typings для output OpenAPI, пишем **тесты на все слои**: unit-тесты для mappers (SQL type → JSON Schema type, FK → reference), интеграционные для полного flow (DDL → OpenAPI YAML), snapshot-тесты для стабильности вывода, property-based тесты для edge-cases (пустые таблицы, циклические FK, экзотические типы SQL), проектируем **backward compatibility и версионирование**: поддерживаем OpenAPI 3.0 и 3.1, при изменении SQL-схемы умеем генерировать diff и changelog в OpenAPI, версионируем сам генератор (semver), документируем breaking changes в mapping rules, думаем о **производительности**: не парсим огромные DDL файлы заново каждый раз, кешируем промежуточные результаты, поддерживаем инкрементальную обработку (только изменённые таблицы), для больших схем (1000+ таблиц) используем streaming и батчинг, закладываем **расширяемость через плагины**: plugin-система для кастомных mappers (например, специфичные для PostGIS типы или Oracle-диалекты), hooks для post-processing (добавление x-internal флагов, удаление deprecated endpoints), используем events/callbacks для интеграции с внешними системами, документируем **архитектурные решения** (ADRs): почему выбрали такой SQL-парсер, почему domain-модель отделена от OpenAPI AST, как работает merge-стратегия при инкрементальной генерации, какие mapping-конвенции по умолчанию и почему, поддерживаем **community и открытость**: если open-source — чёткий README, примеры use-cases, contribution guidelines, если internal tool — внутренняя документация, runbooks, обучающие сессии, проектируем генератор так, чтобы новый разработчик по структуре проекта и интерфейсам быстро понял: где domain (что такое Table, Resource), где application (какие use-cases), где инфраструктура (как парсим SQL, как пишем YAML), какие зависимости допустимы, и на всех уровнях SQL to YAML OpenAPI Generator via DDD держим: строгие границы между доменом, приложением и инфраструктурой, явные контракты и ubiquitous language, слабую связность модулей, высокую тестируемость, конфигурируемость и расширяемость, чтобы инструмент можно было эволюционировать без переписывания с нуля при изменении требований или поддержке новых диалектов SQL и версий OpenAPI.
---
start not from parsing SQL and generating YAML but from understanding the **domain and business goals of the generator**, define what problems the tool solves (accelerate API documentation, sync DB schema with OpenAPI, automate contracts), who it serves (backend devs, architects, DevOps, API-first teams), what data sources it uses (DDL, database information schema, ORM migrations, existing OpenAPI specs), what output artifacts and format (OpenAPI 3.0/3.1, Swagger 2.0, JSON/YAML), split **architectural layers** clearly: domain (conceptual model: Table, Column, Constraint, Relationship, Resource, Operation, Schema, Response), application/use-cases (scenarios: ParseDDL, InferRESTEndpoints, GenerateOpenAPISpec, ValidateMapping, EnrichWithBusinessRules), infrastructure (SQL parsers, DB drivers, YAML serializers, filesystem, template engines), presentation (CLI, web UI, IDE plugins, CI/CD integrations), build the **domain model** as the core: Database (collection of tables and relationships), Table (name, columns, keys, indexes, metadata), Column (name, SQL type, nullable, default, constraints), ForeignKey (source, target, cascade), Index, Constraint, APIResource (endpoint path, HTTP methods, request/response schemas), Operation (GET/POST/PUT/DELETE, params, request body, response codes), Schema (OpenAPI schema object), Component (reusable schemas, parameters, responses), do not mix SQL parsing and YAML generation in one class, separate reading DB schema from producing OpenAPI, design **bounded contexts**: SQL Schema Context (understanding DB structure), API Design Context (REST resources, operations, contracts), OpenAPI Generation Context (spec assembly, formatting, validation), keep explicit boundaries and translations between contexts via anti-corruption layers, use **ubiquitous language**: Table → Resource, Column → Property/Field, PrimaryKey → identifier, ForeignKey → relationship/reference, NOT NULL → required, map SQL types to JSON Schema types deliberately (VARCHAR → string, INT → integer, TIMESTAMP → string format date-time), reflect all terms in code, comments, and docs consistently, design **use-cases as separate services/commands**: ParseSQLSchema (reads DDL or connects to DB, builds domain model Database), InferRESTResources (turns tables into REST resources by convention or config), MapColumnsToSchemas (creates OpenAPI schema definitions from table columns), GenerateOperations (for each resource generates CRUD operations with params and bodies), EnrichWithMetadata (adds descriptions, examples, tags from annotations or external sources), ValidateOpenAPISpec (checks correctness of generated spec), SerializeToYAML (serializes domain OpenAPI model to YAML file), each use-case has one clear input (command/query) and output (result/event), separate **infrastructure**: SQLParser (adapter over SQL parser: ANTLR, sqlparse, JSqlParser), DatabaseIntrospector (connects to live DB via JDBC/psycopg/mysql-connector and reads information schema), YAMLSerializer (turns domain OpenAPI model into YAML via library: PyYAML, js-yaml, SnakeYAML), TemplateEngine (if using templates for parts of spec), FileSystemAdapter (read/write files), ConfigLoader (reads generator config: mapping rules, naming conventions, tags), all adapters implement interfaces defined in the domain, not dictating its shape, code **against abstractions**: domain depends on ISQLSchemaReader, IOpenAPIWriter, ITemplateRenderer, not concrete libraries, inject dependencies via constructors/DI, avoid global singletons and static parser calls, design **SQL → OpenAPI mapping as a set of strategies and rules**: TableToResourceMapper (convention: users table → /users resource), ColumnToPropertyMapper (types, formats, constraints → JSON Schema), RelationshipToLinkMapper (foreign keys → links or nested schemas), use Strategy/Visitor pattern to traverse domain model and apply rules, build **extensibility points**: users can define custom mapping rules (via DSL, config, plugins), override naming conventions (snake_case → camelCase, singular → plural), add metadata (descriptions, examples, deprecated flags) via SQL comments or separate file, design **validation at every stage**: validate input SQL (syntax, semantics), validate domain model (all tables have PK, no circular dependencies without ON DELETE CASCADE, all FKs reference existing tables), validate generated OpenAPI spec against JSON Schema OpenAPI 3.x, output clear errors with location, separate **read-models and write-models**: ParsedSchema (clean representation of DB structure), OpenAPISpecification (clean representation of OpenAPI document), intermediate models for transformations, do not mutate shared state, use immutable structures where possible, design **error handling** as part of the contract: ParseError (SQL parse failure), MappingError (could not map type, missing PK), ValidationError (invalid OpenAPI spec), SerializationError (YAML write failure), distinguish recoverable vs non-recoverable errors, log details for debugging, show users understandable messages, build **configurability and rules**: YAML or JSON config with mapping rules, naming conventions, ignored tables, custom descriptions, tags, security schemes, support config profiles (REST-style, GraphQL-style, RPC-style), do not hardcode all rules in code, make the tool **idempotent**: running the generator twice with the same inputs gives the same output, do not accumulate junk, do not change existing files unpredictably, support **incremental generation**: if DB schema updates (table or column added), know how to merge with existing OpenAPI spec while preserving manual edits (descriptions, examples), use comments/annotations in YAML to mark generated sections, design **observability**: log stages (parsing → mapping → validation → serialization), output summary (how many tables processed, how many endpoints generated, how many errors), support dry-run mode (show what would be generated without writing), design **CLI and API**: CLI with flags (input SQL file/DB connection string, output YAML path, config path, verbosity, format), programmatic API for CI/CD integration (function generateOpenAPI(source, config) → spec), plugins for popular ORMs (Prisma, TypeORM, Alembic, Django migrations), structure **the project by layers**: domain/ (models Database, Table, Column, OpenAPISpec, Operation), application/ (use-cases: ParseSchema, GenerateSpec, Validate), infrastructure/ (parsers, serializers, db-connectors, file-io), cli/ (CLI entry point, arg parsing), config/ (default config, schema validation), tests/ (unit tests for domain and use-cases, integration for full pipeline), avoid utils/helpers folders without context, use **typing and contracts**: strong types for all domain models (TypeScript interfaces, Python dataclasses/Pydantic, Java records), validate config via JSON Schema, generate typings for output OpenAPI, write **tests at all layers**: unit tests for mappers (SQL type → JSON Schema type, FK → reference), integration for full flow (DDL → OpenAPI YAML), snapshot tests for output stability, property-based tests for edge cases (empty tables, circular FKs, exotic SQL types), design **backward compatibility and versioning**: support OpenAPI 3.0 and 3.1, when SQL schema changes, generate diff and changelog in OpenAPI, version the generator itself (semver), document breaking changes in mapping rules, think about **performance**: do not reparse huge DDL files every time, cache intermediate results, support incremental processing (only changed tables), for large schemas (1000+ tables) use streaming and batching, build **extensibility via plugins**: plugin system for custom mappers (e.g. PostGIS-specific types or Oracle dialects), hooks for post-processing (add x-internal flags, remove deprecated endpoints), use events/callbacks for integration with external systems, document **architectural decisions** (ADRs): why we chose this SQL parser, why domain model is separate from OpenAPI AST, how merge strategy works in incremental generation, what default mapping conventions are and why, support **community and openness**: if open-source — clear README, example use-cases, contribution guidelines, if internal tool — internal docs, runbooks, training sessions, design the generator so a new developer can quickly understand from project structure and interfaces: where domain is (what Table, Resource mean), where application is (which use-cases), where infrastructure is (how we parse SQL, how we write YAML), which dependencies are allowed, and at all levels of the SQL to YAML OpenAPI Generator via DDD keep strict boundaries between domain, application, and infrastructure, explicit contracts and ubiquitous language, loose coupling of modules, high testability, configurability and extensibility, so the tool can evolve without rewrites when requirements change or new SQL dialects and OpenAPI versions need support.
####General Excel I/O (reading & writing) using openpyxl
####
understand the business purpose of each Excel file (import, export, report, integration), clearly define what Excel is responsible for and what it is not, treat Excel strictly as an IO boundary not a business layer, define explicit use cases such as “import customers from Excel” or “export monthly report”, design the system so Excel files are interchangeable with other inputs (CSV, API, DB), isolate all openpyxl usage in a dedicated infrastructure adapter, never reference openpyxl from domain or application layers, define clear application-layer services that orchestrate Excel read/write operations, use DTOs or plain data structures for data transfer between layers, never pass domain entities directly to openpyxl, map Excel rows to DTOs at the boundary, validate structure and headers immediately when reading Excel files, fail fast on missing or invalid columns, separate Excel parsing from business validation, enforce business rules inside the domain not in Excel parsing code, define explicit schemas for each Excel format (sheet names, columns, types), version Excel formats when they evolve, keep Excel schemas backward compatible when possible, define reader and writer interfaces (ports) such as ExcelReader and ExcelWriter, let openpyxl implementations act as adapters, inject Excel adapters via constructors or factories, avoid global workbook or worksheet access, keep read and write responsibilities separate (do not mix import and export logic), design idempotent import operations where possible, handle partial failures explicitly (row-level errors vs file-level errors), collect and report validation errors with row numbers, avoid silent row skipping, log file-level events and import/export outcomes, avoid logging sensitive cell data, keep Excel IO free of database or network calls, batch domain operations outside of Excel loops to avoid performance issues, stream large files carefully and document memory limits of openpyxl, optimize by minimizing cell-by-cell operations, centralize date, number, and currency formatting rules, never rely on Excel formatting as a source of truth, treat cell values as raw input only, normalize data types explicitly (dates, decimals, enums), avoid magic column indexes—use named mappings, keep Excel column definitions close to the adapter code, design tests for Excel adapters using small sample files, test domain logic without Excel involved, test application services using mocked Excel ports, add integration tests that read and write real .xlsx files, keep test files small and readable, document supported Excel formats and examples, move file paths and limits to configuration, never hardcode file locations, handle file locking and concurrent access errors explicitly, design exports to be deterministic and repeatable, avoid embedding business decisions in Excel formulas, prefer writing plain values over formulas unless explicitly required, clearly document when formulas are used, ensure generated files open cleanly in common spreadsheet tools, keep the Excel adapter simple and boring, prefer clarity over clever abstractions, treat Excel as an unreliable external system, design for malformed files and user mistakes, keep responsibilities small and focused, and always preserve strict boundaries between Excel IO, application orchestration, and core domain logic