refactor: reduce FollowRepository surface area with query objects #12

Open
opened 2026-05-30 01:32:24 +00:00 by GKaszewski · 0 comments
Owner

Problem

FollowRepository has 19 async methods mixing abstraction levels: single-item CRUD (add_follower, remove_follower), paginated queries (get_followers_page, get_accepted_followers_page), counts (count_followers, count_accepted_followers), status management (update_follower_status, update_following_status), and migration (migrate_follower_actor).

Consumers must implement all 19 methods. Tests need 19 stub implementations even when testing one scenario. Adding a method breaks all downstream code.

Similar issues exist in ActorRepository (7 methods mixing keypairs, remote cache, and announces) and BlocklistRepository (8 methods mixing domain and per-user blocks).

Proposal

Option A — Query objects: Replace groups of related methods with a single query method:

async fn query_followers(&self, q: FollowerQuery) -> Result<FollowerResult>;

enum FollowerQuery {
    Page { user_id: Uuid, offset: u32, limit: usize, status: Option<FollowerStatus> },
    Count { user_id: Uuid, status: Option<FollowerStatus> },
    Inboxes { user_id: Uuid },
}

Option B — Split into focused sub-traits: InboundFollowRepository (followers), OutboundFollowRepository (following), FollowMigrationRepository.

Option C — Default implementations: Provide default impls for paginated/count methods that delegate to the base methods. Consumers only override for performance.

Files

  • src/repository/follow.rs (19 methods)
  • src/repository/actor.rs (7 methods)
  • src/repository/blocklist.rs (8 methods)
  • All downstream consumers (thoughts, movies-diary)

Trade-offs

  • Query objects reduce method count but add enum complexity
  • Sub-traits are cleaner but require more Arc wiring at the builder level
  • Default impls are least disruptive but don't solve the abstraction-level mixing
  • Any option is a breaking change for consumers

Dependency

Should be done alongside or before test mock builder (#15) since the trait shape determines mock ergonomics.

## Problem `FollowRepository` has 19 async methods mixing abstraction levels: single-item CRUD (`add_follower`, `remove_follower`), paginated queries (`get_followers_page`, `get_accepted_followers_page`), counts (`count_followers`, `count_accepted_followers`), status management (`update_follower_status`, `update_following_status`), and migration (`migrate_follower_actor`). Consumers must implement all 19 methods. Tests need 19 stub implementations even when testing one scenario. Adding a method breaks all downstream code. Similar issues exist in `ActorRepository` (7 methods mixing keypairs, remote cache, and announces) and `BlocklistRepository` (8 methods mixing domain and per-user blocks). ## Proposal Option A — **Query objects**: Replace groups of related methods with a single query method: ```rust async fn query_followers(&self, q: FollowerQuery) -> Result<FollowerResult>; enum FollowerQuery { Page { user_id: Uuid, offset: u32, limit: usize, status: Option<FollowerStatus> }, Count { user_id: Uuid, status: Option<FollowerStatus> }, Inboxes { user_id: Uuid }, } ``` Option B — **Split into focused sub-traits**: `InboundFollowRepository` (followers), `OutboundFollowRepository` (following), `FollowMigrationRepository`. Option C — **Default implementations**: Provide default impls for paginated/count methods that delegate to the base methods. Consumers only override for performance. ## Files - `src/repository/follow.rs` (19 methods) - `src/repository/actor.rs` (7 methods) - `src/repository/blocklist.rs` (8 methods) - All downstream consumers (thoughts, movies-diary) ## Trade-offs - Query objects reduce method count but add enum complexity - Sub-traits are cleaner but require more Arc wiring at the builder level - Default impls are least disruptive but don't solve the abstraction-level mixing - Any option is a breaking change for consumers ## Dependency Should be done alongside or before test mock builder (#15) since the trait shape determines mock ergonomics.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: GKaszewski/k-ap#12