Skip to content
This repository was archived by the owner on Jul 23, 2024. It is now read-only.
This repository was archived by the owner on Jul 23, 2024. It is now read-only.

Questions about more complex scenarios using SeaORM and clean architecture #15

@frederikhors

Description

@frederikhors

@bkonkle I saw that you answered "on video" to the other two issues and soon I will be able to watch and learn from the wonder you are creating.

Since you are a content creator perhaps the following question could offer you more food for thought and therefore videos! : smile:


I have a project with these models and relations:

  1. Player
    1. Team
      1. Games
      2. Coach
      3. Doctor
        1. MedicalSpecialty
    2. Sponsor

This is an example, of course, but as you can see I can have on table Player a team_id column as FK (Foreign Key) for Team which has coach_id column as FK for Coach table.

In my Golang backend I'm using an ORM that allows you to (generate and) use this kind of code:

type Player struct {
  id string
  name string
  Team *Team
  //...
}

type Team struct {
  id string
  Coach *Coach
  //...
}

type Coach struct {
  id string
  //...
}

func (repo *Repo) PlayerById(id string) DomainPlayer {
  player := repo.queryById(id).withTeam().withCoach()

  // player here has TEAM and TEAM.COACH using a unique SQL query

  // and I can easily convert it:

  return ConvertPlayerToDomainPlayer(player)
}

Starting with Rust I was fascinated by SeaORM but now that I'm trying to create something more complex I have realized that I cannot do things like in Go.

I'm generating entities using sea-orm-cli from my Postgresql DB created using SeaORM Migrator (this is totally optional, you can also write the following code by yourself).

The generated entities are like this:

//! SeaORM Entity. Generated by sea-orm-codegen 0.9.3

use sea_orm::entity::prelude::*;

#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "player")]
pub struct Model {
    #[sea_orm(primary_key, unique)]
    pub id: String,
    pub name: String,
    pub team_id: Option<String>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
    #[sea_orm(
        belongs_to = "super::team::Entity",
        from = "Column::TeamId",
        to = "super::team::Column::Id",
        on_update = "NoAction",
        on_delete = "NoAction"
    )]
    Team,
}

impl Related<super::team::Entity> for Entity {
    fn to() -> RelationDef {
        Relation::Team.def()
    }
}

impl ActiveModelBehavior for ActiveModel {}

And I'm using code like:

async fn player_by_id(&self, id: String) -> Result<Option<DomainPlayer>> {
    let player = match Player::find_by_id(id)
        .find_also_related(Team)
        // I would like to use here something like `with_team()` and `with_coach()` or `with_team(|team| team.with_coach())`
        .one(self.db)
        .await?
    {
        Some(o) => o,
        None => return Ok(None),
    };

    let coach = if let Some(team) = &player.1 {
        Coach::find_by_id(team.coach_id).one(self.db).await?
    } else {
        None
    };

    // Since I need to return a DomainPlayer (not a Player) here I cannot use a `Player.into()` because generated `Player` does not have `Team` field

    Ok(Some(custom_func_to_transform_player_to_domain_player(player, player.1 /*this is team*/, coach)))

    // Do you can imagine how complex is to use these functions?
}

I'd like to know what you think of this mental order.

Most likely I need to change it because the code is really ugly (as well as expensive for the many SQL queries and allocations) and inconvenient to use to scale with even more complexity.

What do you recommend?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions