Skip to content

Instantly share code, notes, and snippets.

@siemensikkema
Created October 8, 2019 10:21
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save siemensikkema/fada3547536e64f863275c8b957fde2a to your computer and use it in GitHub Desktop.
Save siemensikkema/fada3547536e64f863275c8b957fde2a to your computer and use it in GitHub Desktop.
Example of how we can use repositories in Vapor
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()
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment