# Domain-Driven Design Analysis: Smesh Nostr Client ## Executive Summary This document provides a Domain-Driven Design (DDD) analysis of the Smesh codebase, a React/TypeScript Nostr client. The analysis covers the current domain layer implementation, evaluates progress against DDD principles, and provides recommendations for continued evolution. **Current Status (January 2026):** - Domain layer established with explicit bounded contexts - Core value objects implemented (Pubkey, RelayUrl, EventId, Timestamp) - Rich aggregates created (FollowList, MuteList, PinnedUsersList, RelaySet, RelayList, FavoriteRelays, BookmarkList, PinList) - Domain events defined for social context - Application services layer started (PublishingService, RelaySelector) - Migration adapters enable incremental adoption - **All key providers refactored to use domain aggregates:** - FollowListProvider → FollowList aggregate - MuteListProvider → MuteList aggregate - PinnedUsersProvider → PinnedUsersList aggregate - FavoriteRelaysProvider → FavoriteRelays, RelaySet, RelayUrl - BookmarksProvider → BookmarkList aggregate - PinListProvider → PinList aggregate **Remaining Work:** - Integrate repositories into providers (replace direct service calls) - Create Feed bounded context with proper domain model --- ## 1. Domain Layer Architecture ### 1.1 Directory Structure ``` src/ ├── domain/ # Core domain logic │ ├── index.ts # Domain layer exports │ ├── shared/ # Shared Kernel │ │ ├── value-objects/ │ │ │ ├── Pubkey.ts ✓ Implemented │ │ │ ├── RelayUrl.ts ✓ Implemented │ │ │ ├── EventId.ts ✓ Implemented │ │ │ └── Timestamp.ts ✓ Implemented │ │ ├── errors.ts ✓ Domain errors │ │ └── adapters.ts ✓ Migration helpers │ ├── identity/ # Identity Bounded Context │ │ ├── Account.ts ✓ Entity │ │ ├── SignerType.ts ✓ Value Object │ │ ├── errors.ts ✓ Domain errors │ │ └── adapters.ts ✓ Migration helpers │ ├── social/ # Social Graph Bounded Context │ │ ├── FollowList.ts ✓ Aggregate │ │ ├── MuteList.ts ✓ Aggregate │ │ ├── PinnedUsersList.ts ✓ Aggregate │ │ ├── events.ts ✓ Domain Events │ │ ├── repositories.ts ✓ Repository interfaces │ │ ├── errors.ts ✓ Domain errors │ │ └── adapters.ts ✓ Migration helpers │ ├── relay/ # Relay Bounded Context │ │ ├── RelaySet.ts ✓ Aggregate │ │ ├── RelayList.ts ✓ Aggregate │ │ ├── FavoriteRelays.ts ✓ Aggregate │ │ ├── repositories.ts ✓ Repository interfaces │ │ ├── errors.ts ✓ Domain errors │ │ └── adapters.ts ✓ Migration helpers │ └── content/ # Content Bounded Context │ ├── Note.ts ✓ Entity │ ├── Reaction.ts ✓ Value Object │ ├── Repost.ts ✓ Value Object │ ├── BookmarkList.ts ✓ Aggregate │ ├── PinList.ts ✓ Aggregate │ ├── errors.ts ✓ Domain errors │ └── adapters.ts ✓ Migration helpers │ ├── application/ # Application Services │ ├── PublishingService.ts ✓ Domain Service │ └── RelaySelector.ts ✓ Domain Service │ ├── infrastructure/ # Infrastructure Layer │ └── persistence/ # Repository implementations │ ├── FollowListRepositoryImpl.ts ✓ │ ├── MuteListRepositoryImpl.ts ✓ │ ├── PinnedUsersListRepositoryImpl.ts ✓ │ ├── RelayListRepositoryImpl.ts ✓ │ ├── RelaySetRepositoryImpl.ts ✓ │ ├── FavoriteRelaysRepositoryImpl.ts ✓ │ ├── BookmarkListRepositoryImpl.ts ✓ │ └── PinListRepositoryImpl.ts ✓ │ ├── providers/ # React Context (presentation layer) ├── services/ # Infrastructure services ├── components/ # UI components └── lib/ # Legacy utilities (being migrated) ``` ### 1.2 Bounded Contexts ``` ┌─────────────────────────────────────────────────────────────────────┐ │ CONTEXT MAP │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ ┌───────────────┐ ┌─────────────────┐ │ │ │ Identity │ Partnership │ Social Graph │ │ │ │ Context │◄───────────────────►│ Context │ │ │ │ │ │ │ │ │ │ • Account │ │ • FollowList │ │ │ │ • SignerType │ │ • MuteList │ │ │ │ │ │ • PinnedUsersList│ │ │ └───────┬───────┘ └───────┬─────────┘ │ │ │ │ │ │ │ Customer/Supplier │ Partnership │ │ ▼ ▼ │ │ ┌───────────────┐ ┌───────────────┐ │ │ │ Content │ │ Feed │ │ │ │ Context │ │ Context │ │ │ │ │ │ │ │ │ │ • Note │ │ (Providers) │ │ │ │ • Reaction │ │ │ │ │ │ • Repost │ │ │ │ │ │ • BookmarkList│ │ │ │ │ │ • PinList │ │ │ │ │ └───────────────┘ └───────┬───────┘ │ │ │ │ │ │ └──────────────┬───────────────────────┘ │ │ ▼ │ │ ┌───────────────┐ │ │ │ Relay │ │ │ │ Context │ │ │ │ │ │ │ │ • RelayUrl │ │ │ │ • RelaySet │ │ │ │ • RelayList │ │ │ └───────────────┘ │ │ │ │ ┌───────────────────────────────────────────────────────────┐ │ │ │ SHARED KERNEL │ │ │ │ Pubkey | EventId | Timestamp | RelayUrl | DomainError │ │ │ └───────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` --- ## 2. Implementation Details ### 2.1 Value Objects (Shared Kernel) All value objects follow DDD principles: immutable, self-validating, equality by value. | Value Object | Status | Key Features | |--------------|--------|--------------| | `Pubkey` | ✓ Complete | Hex/npub/nprofile parsing, validation, formatting | | `RelayUrl` | ✓ Complete | URL normalization, WebSocket validation, secure/onion detection | | `EventId` | ✓ Complete | Hex/nevent parsing, validation | | `Timestamp` | ✓ Complete | Unix timestamp wrapper, date conversion | **Example: Pubkey Value Object** ```typescript // Self-validating factory methods const pubkey = Pubkey.fromHex('abc123...') // throws InvalidPubkeyError const pubkey = Pubkey.fromNpub('npub1...') // throws InvalidPubkeyError const pubkey = Pubkey.tryFromString('...') // returns null on invalid // Immutable with rich behavior pubkey.hex // "abc123..." pubkey.npub // "npub1..." pubkey.formatted // "abc123...xyz4" pubkey.formatNpub(12) // "npub1abc...xyz" // Equality by value pubkey1.equals(pubkey2) // true if same hex ``` ### 2.2 Aggregates #### FollowList Aggregate (Social Context) ```typescript class FollowList { // Factory methods static empty(owner: Pubkey): FollowList static fromEvent(event: Event): FollowList // Invariants enforced follow(pubkey): FollowListChange // throws CannotFollowSelfError unfollow(pubkey): FollowListChange // Rich behavior isFollowing(pubkey): boolean getFollowing(): Pubkey[] getEntries(): FollowEntry[] setPetname(pubkey, name): boolean merge(other: FollowList): void // Persistence support toTags(): string[][] toDraftEvent(): DraftEvent } ``` #### MuteList Aggregate (Social Context) ```typescript class MuteList { // Factory methods static empty(owner: Pubkey): MuteList static fromEvent(event: Event, decryptedPrivateTags: string[][]): MuteList // Invariants enforced mutePublicly(pubkey): MuteListChange // throws CannotMuteSelfError mutePrivately(pubkey): MuteListChange unmute(pubkey): MuteListChange // Rich behavior isMuted(pubkey): boolean getMuteVisibility(pubkey): MuteVisibility | null switchToPrivate(pubkey): MuteListChange switchToPublic(pubkey): MuteListChange // Persistence support toPublicTags(): string[][] toPrivateTags(): string[][] // For NIP-04 encryption toDraftEvent(encryptedContent): DraftEvent } ``` #### PinnedUsersList Aggregate (Social Context) ```typescript class PinnedUsersList { // Factory methods static empty(owner: Pubkey): PinnedUsersList static fromEvent(event: Event): PinnedUsersList // Invariants enforced pin(pubkey): PinnedUsersListChange // throws Error if pinning self unpin(pubkey): PinnedUsersListChange // Rich behavior isPinned(pubkey): boolean getPinnedPubkeys(): Pubkey[] getEntries(): PinnedUserEntry[] getPublicEntries(): PinnedUserEntry[] getPrivateEntries(): PinnedUserEntry[] // Private pins support (NIP-04 encrypted) setPrivatePins(privateTags: string[][]): void setEncryptedContent(content: string): void // Persistence support toTags(): string[][] // Public pins toPrivateTags(): string[][] // For NIP-04 encryption toDraftEvent(): DraftEvent } ``` #### RelaySet Aggregate (Relay Context) ```typescript class RelaySet { // Factory methods static create(name: string, id?: string): RelaySet static createWithRelays(name: string, relayUrls: string[], id?: string): RelaySet static fromEvent(event: Event): RelaySet // Invariants enforced (valid URLs, no duplicates) addRelay(relay: RelayUrl): RelaySetChange removeRelay(relay: RelayUrl): RelaySetChange // Rich behavior rename(newName: string): void hasRelay(relay: RelayUrl): boolean getRelays(): RelayUrl[] // Persistence support toTags(): string[][] toDraftEvent(): DraftEvent } ``` #### BookmarkList Aggregate (Content Context) ```typescript class BookmarkList { // Factory methods static empty(owner: Pubkey): BookmarkList static fromEvent(event: Event): BookmarkList static tryFromEvent(event: Event | null): BookmarkList | null // Rich behavior addFromEvent(event: Event): BookmarkListChange addEvent(eventId: EventId, pubkey?: Pubkey): BookmarkListChange addReplaceable(coordinate: string): BookmarkListChange remove(idOrCoordinate: string): BookmarkListChange removeFromEvent(event: Event): BookmarkListChange // Query methods isBookmarked(idOrCoordinate: string): boolean hasEventId(eventId: string): boolean hasCoordinate(coordinate: string): boolean getEntries(): BookmarkEntry[] getEventIds(): string[] getReplaceableCoordinates(): string[] // Persistence support toTags(): string[][] toDraftEvent(): DraftEvent } ``` #### PinList Aggregate (Content Context) ```typescript class PinList { // Factory methods static empty(owner: Pubkey): PinList static fromEvent(event: Event): PinList static tryFromEvent(event: Event | null): PinList | null // Invariants enforced pin(event: Event): PinListChange // throws CannotPinOthersContentError, CanOnlyPinNotesError unpin(eventId: string): PinListChange unpinEvent(event: Event): PinListChange // Query methods isPinned(eventId: string): boolean getEntries(): PinEntry[] getEventIds(): string[] getEventIdSet(): Set get isFull(): boolean // max 5 pins // Persistence support toTags(): string[][] toDraftEvent(): DraftEvent } ``` ### 2.3 Entities #### Note Entity (Content Context) ```typescript class Note { // Factory method static fromEvent(event: Event): Note static tryFromEvent(event: Event | null): Note | null // Identity get id(): EventId get author(): Pubkey // Rich behavior get noteType(): 'root' | 'reply' | 'quote' get isRoot(): boolean get isReply(): boolean get mentions(): NoteMention[] get references(): NoteReference[] get hashtags(): string[] get hasContentWarning(): boolean get contentWarning(): string | undefined // Query methods mentionsUser(pubkey: Pubkey): boolean referencesNote(eventId: EventId): boolean hasHashtag(hashtag: string): boolean } ``` #### Account Entity (Identity Context) ```typescript class Account { // Factory methods static create(pubkey: Pubkey, signerType: SignerType, credentials?: AccountCredentials): Account static fromRaw(pubkeyHex: string, signerTypeValue: string, credentials?: AccountCredentials): Account static fromLegacy(legacy: TAccount): Account | null // Identity and behavior get id(): string // pubkey:signerType get pubkey(): Pubkey get signerType(): SignerType get canSign(): boolean get isViewOnly(): boolean equals(other: Account): boolean hasSamePubkey(other: Account): boolean // Persistence support toLegacy(): TAccount toPointer(): { pubkey: string; signerType: string } } ``` ### 2.4 Domain Events ```typescript // Base event abstract class DomainEvent { readonly occurredAt: Timestamp abstract get eventType(): string } // Social context events class UserFollowed extends DomainEvent { eventType = 'social.user_followed' constructor(actor: Pubkey, followed: Pubkey, relayHint?: string, petname?: string) } class UserUnfollowed extends DomainEvent { eventType = 'social.user_unfollowed' constructor(actor: Pubkey, unfollowed: Pubkey) } class UserMuted extends DomainEvent { eventType = 'social.user_muted' constructor(actor: Pubkey, muted: Pubkey, visibility: MuteVisibility) } class UserUnmuted extends DomainEvent { eventType = 'social.user_unmuted' constructor(actor: Pubkey, unmuted: Pubkey) } class MuteVisibilityChanged extends DomainEvent { eventType = 'social.mute_visibility_changed' constructor(actor: Pubkey, target: Pubkey, from: MuteVisibility, to: MuteVisibility) } class FollowListPublished extends DomainEvent class MuteListPublished extends DomainEvent ``` ### 2.5 Application Services ```typescript // PublishingService - creates draft events class PublishingService { createNoteDraft(content: string, options: PublishNoteOptions): DraftEvent createReactionDraft(targetEventId: string, targetPubkey: string, targetKind: number, emoji: string): DraftEvent createRepostDraft(targetEventId: string, targetPubkey: string, embeddedContent?: string): DraftEvent createFollowListDraft(followList: FollowList): DraftEvent createMuteListDraft(muteList: MuteList, encryptedPrivateMutes: string): DraftEvent createRelayListDraft(relayList: RelayList): DraftEvent extractMentionsFromContent(content: string): Pubkey[] extractHashtagsFromContent(content: string): string[] } // RelaySelector - determines relays for publishing class RelaySelector { selectWriteRelays(userRelayList: RelayList): RelayUrl[] selectReadRelays(userRelayList: RelayList): RelayUrl[] selectForUser(pubkey: Pubkey, userRelayList: RelayList): RelayUrl[] mergeRelays(sources: RelayUrl[][]): RelayUrl[] } ``` ### 2.6 Migration Adapters Each bounded context provides adapters for incremental migration from legacy code: ```typescript // Pubkey adapters toPubkey(hex: string): Pubkey // Create from hex tryToPubkey(hex: string): Pubkey | null // Safe creation fromPubkey(pubkey: Pubkey): string // Extract hex toPubkeys(hexArray: string[]): Pubkey[] // Bulk conversion fromPubkeys(pubkeys: Pubkey[]): string[] // Bulk extraction createPubkeySet(hexArray: string[]): Set // For fast lookups // FollowList adapters toFollowList(owner: string, hexArray: string[]): FollowList fromFollowListToHexSet(followList: FollowList): Set isFollowingHex(followList: FollowList, hex: string): boolean followByHex(followList: FollowList, hex: string): FollowListChange // MuteList adapters toMuteList(owner: string, publicHexes: string[], privateHexes: string[]): MuteList fromMuteListToHexSet(muteList: MuteList): Set isMutedHex(muteList: MuteList, hex: string): boolean createMuteFilter(muteList: MuteList): (hex: string) => boolean // PinnedUsersList adapters toPinnedUsersList(event: Event, decryptedPrivateTags?: string[][]): PinnedUsersList fromPinnedUsersListToHexSet(list: PinnedUsersList): Set isPinnedHex(list: PinnedUsersList, hex: string): boolean pinByHex(list: PinnedUsersList, hex: string): boolean unpinByHex(list: PinnedUsersList, hex: string): boolean createPinnedFilter(list: PinnedUsersList): (hex: string) => boolean ``` --- ## 3. Anti-Pattern Status | Anti-Pattern | Original Status | Current Status | Progress | |--------------|-----------------|----------------|----------| | **Anemic Domain Model** | High severity | Mostly resolved | Aggregates have behavior; key providers now delegate to domain | | **Smart UI** | Medium severity | Partially resolved | Domain logic moving to aggregates; some UI components still have logic | | **Database-Driven Design** | Low severity | Resolved | Domain model independent of storage | | **Missing Aggregate Boundaries** | Medium severity | Resolved | Clear aggregates with invariants | | **Leaky Abstractions** | Medium severity | Mostly resolved | Domain layer has no infrastructure deps; providers act as thin orchestration layer | --- ## 4. Remaining Work ### 4.1 Phase 3: Provider Integration (Complete) All key providers have been refactored to use domain aggregates: **Completed:** - ✓ `FollowListProvider` → uses `FollowList` aggregate - ✓ `MuteListProvider` → uses `MuteList` aggregate - ✓ `PinnedUsersProvider` → uses `PinnedUsersList` aggregate - ✓ `FavoriteRelaysProvider` → uses `FavoriteRelays`, `RelaySet`, `RelayUrl` - ✓ `BookmarksProvider` → uses `BookmarkList` aggregate - ✓ `PinListProvider` → uses `PinList` aggregate **Example: Refactored Pattern** ```typescript // Before: Logic in provider const addBookmark = async (event: Event) => { const currentTags = bookmarkListEvent?.tags || [] if (currentTags.some(tag => tag[0] === 'e' && tag[1] === event.id)) return const newTags = [...currentTags, buildETag(event.id, event.pubkey)] // manual tag construction... } // After: Delegate to domain const addBookmark = async (event: Event) => { const bookmarkList = tryToBookmarkList(bookmarkListEvent) ?? BookmarkList.empty(owner) const change = bookmarkList.addFromEvent(event) if (change.type === 'no_change') return const draftEvent = bookmarkList.toDraftEvent() await publish(draftEvent) } ``` ### 4.2 Phase 4: Repository Implementation (Complete) Repository implementations created in `src/infrastructure/persistence/`: **Implemented Repositories:** - ✓ `FollowListRepositoryImpl` - Social context - ✓ `MuteListRepositoryImpl` - Social context (with NIP-04 encryption support) - ✓ `PinnedUsersListRepositoryImpl` - Social context (with NIP-04 encryption support) - ✓ `RelayListRepositoryImpl` - Relay context - ✓ `RelaySetRepositoryImpl` - Relay context - ✓ `FavoriteRelaysRepositoryImpl` - Relay context - ✓ `BookmarkListRepositoryImpl` - Content context - ✓ `PinListRepositoryImpl` - Content context **Dependency Injection:** - `RepositoryProvider` - Provides repository instances to React components via context - Located in `src/providers/RepositoryProvider.tsx` - Nested after `NostrProvider` in `App.tsx` to access publish and encryption functions **Usage Pattern:** ```typescript // Create repository with dependencies const followListRepo = new FollowListRepositoryImpl({ publish }) // Find aggregate const followList = await followListRepo.findByOwner(pubkey) // Save aggregate (publishes to relays and updates cache) await followListRepo.save(followList) ``` ### 4.3 Phase 5: Event-Driven Architecture (Complete) Domain event infrastructure implemented in `src/domain/shared/events.ts`: **Event Dispatcher:** - `SimpleEventDispatcher` - In-memory event bus with type-safe handlers - `eventDispatcher` - Global singleton instance - Supports type-specific handlers and catch-all handlers **Event Handlers (`src/application/handlers/`):** - `SocialEventHandlers.ts` - Handles user follow/unfollow, mute/unmute events - `ContentEventHandlers.ts` - Handles bookmark, pin, reaction events **Event Types:** - Social: `UserFollowed`, `UserUnfollowed`, `UserMuted`, `UserUnmuted`, `MuteVisibilityChanged`, `FollowListPublished`, `MuteListPublished` - Content: `EventBookmarked`, `EventUnbookmarked`, `NotePinned`, `NoteUnpinned`, `PinsLimitExceeded`, `ReactionAdded`, `ContentReposted` **Usage Pattern:** ```typescript // Dispatch events from providers await eventDispatcher.dispatch( new EventBookmarked(ownerPubkey, eventId, 'event') ) // Register handlers eventDispatcher.on('content.event_bookmarked', async (event) => { console.log('Bookmarked:', event.bookmarkedEventId) }) ``` --- ## 5. Metrics | Metric | December 2024 | January 2026 | Target | |--------|---------------|--------------|--------| | Value Objects | 0 | 4 types | 4+ ✓ | | Explicit Aggregates | 0 | 8 aggregates | 5+ ✓ | | Domain Events | 0 | 7 event types | 10+ | | Domain Entities | 0 | 3 entities | 5 | | Repository Interfaces | 0 | 8 interfaces | 5+ ✓ | | Repository Implementations | 0 | 8 implementations | 5+ ✓ | | Application Services | 0 | 2 services | 4 | | Providers Using Domain | 0 | 6 providers | 5+ ✓ | | Domain Logic in Providers | ~60% | ~10% | <10% ✓ | --- ## 6. Implementation Checklist - [x] Ubiquitous language documented (Nostr terminology) - [x] Bounded contexts identified (Identity, Social, Content, Relay, Feed) - [x] Context map documented with relationships - [x] Value objects for primitives (Pubkey, RelayUrl, EventId, Timestamp) - [x] Aggregates with clear invariants (FollowList, MuteList, PinnedUsersList, RelaySet, RelayList, FavoriteRelays, BookmarkList, PinList) - [x] Entities with behavior (Account, Note, Reaction, Repost) - [x] Domain events for state changes (Social context events) - [x] Domain errors hierarchy (InvalidPubkeyError, CannotFollowSelfError, CannotPinOthersContentError, etc.) - [x] Migration adapters for incremental adoption - [x] Key providers refactored to use domain aggregates (FollowListProvider, MuteListProvider, PinnedUsersProvider, FavoriteRelaysProvider, BookmarksProvider, PinListProvider) - [x] Repositories abstract persistence (7 interfaces with IndexedDB + Relay implementations) - [x] Event handlers for cross-context communication (SocialEventHandlers, ContentEventHandlers) - [x] Migration from legacy lib/ utilities (completed - all pubkey logic now uses domain Pubkey directly) --- ## 7. Recommendations ### Short-term (Next Sprint) 1. **Migrate providers to use repositories directly** - Replace direct publish calls with repository.save() calls 2. **Refactor NostrProvider** - Consider moving event state management to individual providers using repositories ### Medium-term (Next Quarter) 1. **Create Feed bounded context** with proper domain model 2. **Expand domain event handlers** for more cross-context coordination 3. **Add remaining domain events** (NoteCreated, ProfileUpdated, etc.) ### Long-term 1. **Event sourcing consideration** - Nostr events are naturally event-sourced 2. **CQRS pattern** for read-heavy feed operations --- ## 8. Conclusion The Smesh codebase has made significant progress toward DDD principles: 1. **Established domain layer** with clear bounded contexts 2. **Implemented core value objects** enabling type-safe domain operations 3. **Created rich aggregates** with encapsulated business rules (8 aggregates) 4. **Defined domain events** for social graph and content operations 5. **Provided migration path** via adapter functions 6. **Refactored all key providers** to use domain aggregates (6 providers now delegate to domain) 7. **Implemented repository pattern** with 8 repository interfaces and 8 implementations for persistence abstraction 8. **Added event-driven architecture** with domain event dispatcher and cross-context handlers 9. **Completed lib/ utilities migration** - all pubkey logic now uses domain Pubkey directly 10. **Created RepositoryProvider** for dependency injection of repositories into React components The anemic domain model anti-pattern has been resolved. Providers now act as thin orchestration layers that delegate business logic to domain aggregates. Domain events are dispatched when aggregates change, enabling cross-context communication. **Social context aggregates:** - `FollowList` - Following relationships with petnames and relay hints - `MuteList` - Public and private mutes with NIP-04 encryption - `PinnedUsersList` - Pinned users with public/private support (kind 10003) **Repository infrastructure:** - All 8 repository implementations complete with IndexedDB caching and relay publishing - `RepositoryProvider` provides dependency-injected repository instances - Repositories handle NIP-04 encryption/decryption for private data **lib/ utilities migration completed:** - `lib/pubkey.ts` - Deprecated functions removed; only `generateImageByPubkey` (UI utility) remains - `lib/tag.ts` - Uses `Pubkey.isValidHex()` directly from domain - `lib/nip05.ts` - Uses domain `Pubkey` class directly - `lib/event-metadata.ts` - Uses domain `Pubkey` class directly - `lib/relay.ts`, `lib/event.ts` - Infrastructure utilities (appropriate to keep) - All services, providers, components, and hooks now import directly from `@/domain` The next priorities are: - Migrating providers to use repositories directly for persistence - Creating Feed bounded context with proper domain model --- *Updated: January 2026* *Analysis based on DDD principles from Eric Evans and Vaughn Vernon*