DAOs and Repositories
To manipulate data, we use abstractions around the details for readability and to lessen boilerplate.
There are a couple layers of abstractions in dealing with storage. At NarbaseNarbase, the most notable ones that you will likely deal with are DAOs (Data Access Objects) and Repositories.
DAOs:
For new DAOs, extend `BasicDaoWithoutDelete` and `BasicDao`.
They sit at a lower level than Repositories, we use them for CRUD operations using Kotlin Exposed ORM.library.
aA DAO should represent one and only one object or entity in the domain i.e a user, a company, a school, an order .. etc.
Repositories:
A Repository is closer to the business logic and it is used as a wrapper around more than one DAO. It is yet another layer of abstraction to make the code more DRY and organized.
Examples:
Note that things are simplified in the basic example, this is *NOT* the best way to use tables and models or even DAOs as there is a lot of boilerplate.
we start with basic models
data class Student(
val id: UUID,
val name: String,
val year: Int
)
data class Instructor(
val id: UUID,
val name: String,
)
data class Project(
val id: UUID,
val title: String,
val grader: UUID,
val creator: UUID
)
Then we create our DAOs, we start with Student
:
import tables.StudentTable
import models.Student
object StudentDao {
fun toModel(row: ResultRow): Student {
return Student(
row[Student.id].value,
row[Student.name].value,
row[Student.year].value,
)
}
fun create(model: Student): UUID {
val id = StudentTable
.insert { row ->
row[Student.name] = model.name
row[Student.year] = model.year
}
return id
}
fun update(modifiedStudent: Student) {
StudentTable
.update ({ StudentTable.id eq modifiedStudent.id }){ row ->
row[Student.name] = modifiedStudent.name
row[Student.year] = modifiedStudent.year
}
}
fun delete(studentId: UUID) {
StudentTable
.deleteWhere { table.id eq studentId }
}
fun read(studentId: UUID): List<Student> {
StudentTable
.select { StudentTable.id eq studentId }
.map(::toModel)
}
}
Instructor
DAO:
import tables.InstructorTable
import models.Instructor
object InstructorDao {
fun toModel(row: ResultRow): Instructor {
return InstructorModel(
row[Instructor.id].value,
row[Instructor.name].value,
)
}
fun create(model: Instructor): UUID {
val id = InstructorTable
.insert { row ->
row[Instructor.name] = model.name
}
return id
}
fun update(modifiedInstructor: Instructor) {
InstructorTable
.update ({ Instructor.id eq modifiedInstructor.id }){ row ->
row[Instructor.name] = modifiedInstructor.name
}
}
fun delete(instructorId: UUID) {
InstructorTable
.deleteWhere { table.id eq instructorId }
}
fun read(instructorId: UUID): List<Instructor> {
InstructorTable
.select { InstructorTable.id eq instructorId }
.map(::toModel)
}
}
Project
DAO:
import tables.ProjectTable
import models.Project
object ProjectDao {
fun toModel(row: ResultRow): Project {
return Project(
row[ProjectTable.id].value,
row[ProjectTable.title].value,
row[ProjectTable.grader].value,
row[ProjectTable.creator].value
)
}
fun create(projects: List<Project>) List<UUID> {
return projects.map { project ->
val id = ProjectTable.insert { row ->
row[ProjectTable.title] = project.title
row[ProjectTable.grader] = project.grader
row[ProjectTable.creator] = project.creator
}
id
}
}
fun update(projects: List<Project>) {
return projects.map { project ->
ProjectTable
.update ({ ProjectTable.id eq project.id }){ row ->
row[ProjectTable.title] = project.title
row[ProjectTable.creator] = project.creator
}
}
}
fun getAll(): List<Project> {
return ProjectTable.selectAll().map(::toModel)
}
fun delete(projectId: UUID) {
ProjectTable
.deleteWhere { table.id eq projectId }
}
fun getByProjectId(projectId: UUID): Project {
ProjectTable
.select { ProjectTable.id eq projectId }
.map(::toModel)
}
fun getByStudentId(studentId: UUID): List<Project> {
ProjectTable
.select { ProjectTable.creator eq studentId }
.map { toModel(it) }
}
}
Note how we use object declaration ( a Singleton ) instead of a normal class because we only need one instance of the DAOs
StudentRm stands for Repository Model. See the suffix section for more details about the suffixes used.
Now the repository:
data class StudentInfoDataStudentRm (
val student: Student
val projectInfo: List<Project>
)
object class StudentInfoRepository {
fun create(studentInfo: StudentInfoData)StudentRm) {
transaction {
StudentDao.create(studentInfo.student)
ProjectDao.create(studentInfo.projectInfo)
}
}
fun update(studentInfo: StudentInfoData)StudentRm) {
transaction {
StudentDao.create(studentInfo.student)
ProjectDao.create(studentInfo.projectInfo)
}
}
fun delete(studentId: UUID) {
StudentDao.delete(studentId) // associated Projects will subsequently be deleted because it's cascading
}
fun getInfo(studentId: UUID): StudentInfoDataStudentRm {
return StudentInfoData(StudentRm(
student = StudentDao.read(studentId)
projectInfo = ProjectDao.getByStudentId(studentId)
)
}
}