Swift Academy · Lesson

Use Case and Repository Patterns

Defining use cases as Swift protocols and implementing repository interfaces.

Lesson 2 of 413 steps

Use Case and Repository Patterns is a free Swift Academy lesson on CoddyKit. This is lesson 2 of 4. Any 3 lessons of this learning path are free to read in full — after that, CoddyKit PRO unlocks every lesson, plus hands-on practice with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, and your progress syncs across the web and the CoddyKit app. The Swift Academy course includes 4 lessons in total.

What is a Use Case?

A use case (interactor) encapsulates a single business action, orchestrating domain entities and repositories.

struct PlaceOrderUseCase {
  let cart: CartRepository
  let payment: PaymentService
  let orders: OrderRepository
  func execute(userId: Int) async throws -> Order {
    let items = try await cart.items(for: userId)
    let receipt = try await payment.charge(items: items)
    return try await orders.create(receipt: receipt)
  }
}

Single Responsibility

Each use case does exactly one thing. Multiple responsibilities mean multiple use cases.

// One use case per business action:
struct FetchUserUseCase { ... }
struct UpdateUserUseCase { ... }
struct DeleteUserUseCase { ... }

Use Case as a Protocol

Define a protocol for the use case so it can be mocked in presentation layer tests.

protocol PlacingOrder {
  func execute(userId: Int) async throws -> Order
}
struct PlaceOrderUseCase: PlacingOrder { ... }

What is a Repository?

A repository abstracts the data source. Callers interact with a Swift protocol; the implementation may be network, Core Data, or in-memory.

protocol OrderRepository {
  func create(receipt: PaymentReceipt) async throws -> Order
  func list(userId: Int) async throws -> [Order]
  func cancel(orderId: Int) async throws
}

Repository Implementation

The concrete repository translates domain calls into network or database operations.

struct RemoteOrderRepository: OrderRepository {
  func create(receipt: PaymentReceipt) async throws -> Order {
    let dto = receipt.toDTO()
    let data = try await api.post("/orders", body: dto)
    return try JSONDecoder().decode(OrderDTO.self, from: data).toDomain()
  }
  func list(userId: Int) async throws -> [Order] { ... }
  func cancel(orderId: Int) async throws { ... }
}

In-Memory Repository for Tests

Provide an in-memory repository for fast, deterministic tests without I/O.

final class InMemoryOrderRepo: OrderRepository {
  var orders: [Order] = []
  func create(receipt: PaymentReceipt) async throws -> Order {
    let order = Order(id: orders.count + 1, items: receipt.items)
    orders.append(order)
    return order
  }
  func list(userId: Int) async throws -> [Order] { orders }
  func cancel(orderId: Int) async throws { orders.removeAll { $0.id == orderId } }
}

Composing Use Cases

Use cases can call other use cases for shared sub-operations, keeping each focused.

struct CheckoutUseCase {
  let validateCart: ValidateCartUseCase
  let placeOrder: PlaceOrderUseCase
  let notifyUser: NotifyUserUseCase
  func execute(userId: Int) async throws -> Order {
    try await validateCart.execute(userId: userId)
    let order = try await placeOrder.execute(userId: userId)
    try await notifyUser.execute(order: order)
    return order
  }
}

Repository vs Service

Repository: CRUD over a single entity type. Service: orchestrates multiple entities or calls external APIs without direct storage.

// Repository: single entity
protocol UserRepository { func getUser(id: Int) async throws -> User }
// Service: cross-entity operation
protocol AuthService { func login(email: String, password: String) async throws -> Session }

Caching in Repositories

Repositories can transparently cache results without the use case knowing the implementation detail.

struct CachedUserRepository: UserRepository {
  private var cache: [Int: User] = [:]
  private let remote: UserRepository
  func getUser(id: Int) async throws -> User {
    if let cached = cache[id] { return cached }
    let user = try await remote.getUser(id: id)
    cache[id] = user
    return user
  }
  func saveUser(_ user: User) async throws { try await remote.saveUser(user) }
}

Testability

Because use cases depend on protocol repositories, you can inject stubs to test business logic in isolation.

let repo = InMemoryOrderRepo()
let sut = PlaceOrderUseCase(orders: repo, ...)
let order = try await sut.execute(userId: 1)
XCTAssertEqual(repo.orders.count, 1)

Error Translation

Repositories should translate data-layer errors (network, DB) into domain errors before returning.

func getUser(id: Int) async throws -> User {
  do {
    let data = try await api.get("/users/\(id)")
    return try decode(data)
  } catch let e as URLError where e.code == .notConnectedToInternet {
    throw DomainError.offline
  }
}

Quick Check

What is the key difference between a Repository and a Service in Clean Architecture?

Lesson Recap

Use cases encapsulate single business actions and depend on repository protocols. Repositories abstract data sources behind a protocol. Use in-memory repositories for fast tests. Repositories translate infrastructure errors into domain errors. Services orchestrate multi-entity operations.

Free to start

Learn Swift with an AI tutor — free

Write and run real code in your browser, get instant help from a 24/7 AI tutor, and pick up where you left off on the web or in the app.

Courses
122
Lessons
409

Frequently Asked Questions

Is the “Use Case and Repository Patterns” lesson free?

Yes — any 3 lessons of the Swift Academy learning path, including “Use Case and Repository Patterns”, are free to read in full here on the web. After that, CoddyKit PRO unlocks every lesson, plus interactive practice with a built-in code editor and a 24/7 AI tutor. The Swift Academy course includes 4 lessons in total.

What will I learn in “Use Case and Repository Patterns”?

Defining use cases as Swift protocols and implementing repository interfaces. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners, so you can start here or from the beginning and move at your own pace. This is lesson 2 of 4.

How long does the “Use Case and Repository Patterns” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Layers: Domain, Data and Presentation
  2. Use Case and Repository Patterns
  3. Dependency Inversion with Protocol Abstractions
  4. Wiring Layers Together without a DI Framework
← Back to Swift Academy