2016-12-29 6 views
0

Ich versuche, eine einfache Aggregation Wurzel in Slick zu implementieren. Aber ich weiß nicht wirklich, was der beste Weg ist, das zu tun.Zusammenfassung Wurzel-Implementierung mit Slick

Hier ist meine Domain-Objekte:

case class Project(id: UUID, 
       name: String, 
       state: ProjectState, 
       description: String, 
       team: String, 
       tags: Set[String] 

Ich möchte die "Tags" speichern, in einer separaten Tabelle, und stellen Sie "Projekt" Objekte aus "projects_table" und "project_tags_table" up

Hier ist meine Tabellendefinition:

class ProjectTable(tag: Tag) extends Table[ProjectTableRecord](tag, Some("octopus_service"), "projects") { 

     def id: Rep[UUID] = column[UUID]("id", O.PrimaryKey) 

     def name: Rep[String] = column[String]("name") 

     def state: Rep[ProjectState] = column[ProjectState]("state") 

     def description: Rep[String] = column[String]("description") 

     def team: Rep[String] = column[String]("team") 



     override def * : ProvenShape[ProjectTableRecord] = (id, name, state, description, team, created, lastModified) <> (
     (ProjectTableRecord.apply _).tupled, ProjectTableRecord.unapply 
    ) 
    } 

class ProjectTagTable(tag: Tag) extends Table[ProjectTag](tag, Some("octopus_service"), "project_tags") { 

    def projectID: Rep[UUID] = column[UUID]("project_id") 

    def name: Rep[String] = column[String]("name") 

    def project = foreignKey("PROJECT_FK", projectID, TableQuery[ProjectTable])(_.id, onUpdate = ForeignKeyAction.Restrict, onDelete = ForeignKeyAction.Cascade) 

    override def * : ProvenShape[ProjectTag] = (projectID, name) <> (
    ProjectTag.tupled, ProjectTag.unapply 
) 
} 

Wie kann ich "Projekt" Objekte aus dem Verbinden dieser 2 Tabellen generieren?

Vielen Dank im Voraus :)

Antwort

2

Ich denke, es ein Irrglaube, auf der Ebene der Verantwortung. Mit Slick können Sie auf die relationale Datenbank zugreifen (bis zu einem gewissen Grad auf die gleiche Weise wie SQL dies ermöglicht). Es ist im Grunde eine DAO-Schicht.

Aggregatwurzel ist wirklich eine Ebene darüber (es ist eine Domain-Sache, nicht db Level-Sache - obwohl sie oft in großem Umfang die gleichen sind).

Also im Grunde brauchen Sie obenSlick Tabellen eine Ebene haben, die Sie verschiedene Abfragen und Aggregat die Ergebnisse in einzelnen Wesen führen erlauben würde.

Bevor wir beginnen, obwohl - Sie sollten irgendwo erstellen und speichern Sie Ihre TableQuery Objekte, vielleicht so:

lazy val ProjectTable = TableQuery[ProjectTable] 
lazy val ProjectTagTable = TableQuery[ProjectTagTable] 

Man könnte sie stellen wahrscheinlich irgendwo in der Nähe von Ihnen Table definitions.

Also zuerst, wie ich erwähnt, Ihre Aggregate Root Project muss von etwas gezogen werden. Nennen wir es ProjectRepository.

Sagen wir es wird eine Methode haben def load(id: UUID): Future[Project].

Diese Methode vielleicht würde wie folgt aussehen:

class ProjectRepository { 
    def load(id: UUID): Future[Project] = { 
     db.run(
      project <- ProjectTable.filter(_.id === id).result 
      tags <- ProjectTagTable.filter(_.projectId === id).result 
     ) yield { 
      Project(
       id = project.id, 
       name = project.name, 
       state = project.state, 
       description = project.description, 
       team = project.team, 
       tags = tags.map(_.name) 
      ) 
     } 
    } 

    // another example - if you wanted to extract multiple projects 
    // (in reality you would probably apply some paging here) 
    def findAll(): Future[Seq[Project]] = { 
     db.run(
      ProjectTable 
       .join(ProjectTag).on(_.id === _.projectId) 
       .result 
       .map { _.groupBy(_._1) 
         .map { case (project, grouped) => 
          Project(
           id = project.id, 
           name = project.name, 
           state = project.state, 
           description = project.description, 
           team = project.team, 
           tags = grouped.map(_._2.name) 
          ) 
         } 
       } 
     ) 
    } 
} 

Exkurs: Sie müssten etwas tun Wenn Sie das Paging in findAll Methode haben wollte:

ProjectTable 
    .drop(pageNumber * pageSize) 
    .take(pageSize) 
    .join(ProjectTag).on(_.id === _.projectId) 
    .result 

Above würde Sub-Abfrage erzeugen, aber es ist im Grunde typische Art und Weise, wie Sie mit mehreren verknüpften Relationen paging (ohne Unterabfrage würden Sie über die gesamte Ergebnismenge Seite, die die meisten der t ist ich bin nicht was du brauchst!).

Kommen wir zurück zu Hauptteil:

Natürlich wäre es einfacher, wenn Sie definiert Sie definiert Ihre Project als:

case class Project(project: ProjectRecord, tags: Seq[ProjectTag]) 

dann würde Ihre Ausbeute einfach sein:

yield { 
    Project(project, tags) 
} 

aber das ist definitiv eine Frage des Geschmacks (es macht eigentlich Sinn, es so zu machen wie du - interne rec verstecken ord Layout).

Grundsätzlich gibt es potenziell mehrere Dinge, die hier verbessert werden könnten. Ich bin nicht wirklich ein Experte für DDD aber zumindest aus Slick Perspektive die erste Änderung, die getan werden sollte, ist die Methode zu ändern:

def load(id: UUID): Future[Project] 

zu

def load(id: UUID): DBIO[Project] 

und db.run(...) Betrieb auf einer höheren Ebene durchführen . Der Grund dafür ist, dass Sie in Slick, sobald Sie feuern db.run (also DBIO zu Future konvertieren) verlieren Sie die Fähigkeit, mehrere Operationen innerhalb einzelner Transaktion zu komponieren. Ein gängiges Muster ist also, die Anwendungsschichten auf DBIO zu bringen, im Wesentlichen bis zu einigen Geschäftsebenen, die Transaktionsgrenzen definieren.

+0

ActiveRecord ist kein angemessenes Persistenzmuster für DDD-ARs. – plalx

+0

Genau - das ist der Sinn der obigen Antwort. Ihr root muss (muss) nicht zu einem bestimmten db-Tupel passen, aber Slick funktioniert grundsätzlich auf dieser Ebene. Daher hältst du keine blanke Slick-Ebene als AR. –

+0

Ich meinte, dass ARs nicht auf die db zugreifen sollten. Persistenzdetails sollen in einem Repository abstrahiert werden. Nun, das ist, wenn Sie reines DDD machen wollen, wo sich das Modell nur auf das Geschäft konzentriert. Es gibt keine definitive Regel, aber bei einer komplexen Domänenlogik wollen Sie keine Persistenzlogik hineinwerfen. – plalx