import FluentMySQL import Vapor // MARK: Models final class User: MySQLModel { var id: Int? var active: Bool var courses: Siblings<User, Course, UserCoursePivot> { return siblings() } } final class UserCoursePivot: MySQLPivot { typealias Left = User typealias Right = Course static var leftIDKey: WritableKeyPath<UserCoursePivot, Int> = \.userId static var rightIDKey: WritableKeyPath<UserCoursePivot, Int> = \.courseId var userId: Int var courseId: Int var id: Int? } final class CourseLecturePivot: MySQLPivot { typealias Left = Course typealias Right = Lecture static var leftIDKey: WritableKeyPath<CourseLecturePivot, Int> = \.courseId static var rightIDKey: WritableKeyPath<CourseLecturePivot, Int> = \.lectureId var courseId: Int var lectureId: Int var id: Int? } final class Course: MySQLModel { var id: Int? var lectures: Siblings<Course, Lecture, CourseLecturePivot> { return siblings() } } final class Lecture: MySQLModel { var id: Int? } // MARK: "Controllers" struct APIUserController { // just 1 repository per controller, easy to mock and test let repository: APIUserRepository // ... actual endpoints using the repository come here } struct APICourseController { let repository: APICourseRepository // ... actual endpoints using the repository come here } // MARK: Repository Protocols protocol APIUserRepository: UserCoursesRepository { func users() -> Future<[User]> func activeUsers() -> Future<[User]> } protocol APICourseRepository: UserCoursesRepository { func deleteCourseWithLectures(_ course: Course) -> Future<Void> } // An example of common functionality for multiple repositories. Combined using protocol composition. protocol UserCoursesRepository { func courses(for user: User) -> Future<[Course]> } // MARK: Concrete Repository // the actual database repository consist of mostly generic building blocks. struct MySQLRepository { let pool: DatabaseConnectionPool<ConfiguredDatabase<MySQLDatabase>> func all<M: MySQLModel>( on connection: MySQLConnection, modifyQuery: (QueryBuilder<MySQLDatabase, M>) -> Void = { _ in } ) -> Future<[M]> { let query = M.query(on: connection) modifyQuery(query) return query.all() } func first<M: MySQLModel>( on connection: MySQLConnection ) -> Future<M?> { return M.query(on: connection).first() } } // MARK: Repository conformances // the protocol implementations for the repository use the building blocks to provide the specific // functionality that the controllers need. extension MySQLRepository: APIUserRepository { func users() -> Future<[User]> { return pool.withConnection { self.all(on: $0) } } func activeUsers() -> Future<[User]> { return pool.withConnection { self.all(on: $0) { $0.filter(\.active == true) } } } } extension MySQLRepository: APICourseRepository { // this is an example of a "complex" action where we use a transaction to make sure all can be rolled back func deleteCourseWithLectures(_ course: Course) -> Future<Void> { pool.withConnection { $0.transaction(on: .mysql) { transaction in try course .lectures .query(on: transaction) .delete() .flatMap { course.delete(on: transaction) } } } } } extension MySQLRepository: UserCoursesRepository { func courses(for user: User) -> Future<[Course]> { return pool.withConnection { try user.courses.query(on: $0).all() } } }