Skip to main content

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 Narbase the most notable ones that you will likely deal with are DAOs (Data Access Objects) and Repositories.

DAOs:


They sit at a lower level than Repositories, we use them for CRUD operations using Exposed ORM.

a 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

Now the repository: 

data class StudentInfoData (
	val student: Student
  	val projectInfo: List<Project>
)
object class StudentInfoRepository {
	fun create(studentInfo: StudentInfoData) {
    	transaction {
        	StudentDao.create(studentInfo.student)
            ProjectDao.create(studentInfo.projectInfo)
        }
    }
    
  	fun update(studentInfo: StudentInfoData) {
    	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): StudentInfoData {
    	return StudentInfoData(
        	student = StudentDao.read(studentId)
            projectInfo = ProjectDao.getByStudentId(studentId)
        )
    }
}