# SeaORM ## Some Rant about ORM Implementations #### Entity, ActiveModel, Model SeaORM tries to implement (those #@!$) DDD ideas in much detail, creating this multitude of fixed struct names, like user::Model, user::Entity, user::ActiveModel, where each operation still needs us to import the Trait itself, most code is hidden behind macros, and you still end up having to call "insert" on the active model, instead of a repository object. At least the activemodel, unlike in implementations like Django's ActiveRecord pattern, seems to support dirty flags, given you have to define fields as enums "Set()" and "NotSet" in the model. This probably will make it easier to save models without creating huge SQLs, or micromanaging which fields are touched, or resaving data that was actually unchanged, allowing more transparent PATCH implementations. When it comes to writing ORM Code, SeaORM is rather bulky, and complicated, somehow the opposite of what I expect from an ORM implementation. At least however, it is concise, and you can access all your model parts, like Columns, by the same pattern, and allow nice filter patterns (e.g. user::Column.Name.contains(...)) that could be expanded by implementing additional functions on them. So, once one accepts the concept, it works mostly well, and as it forces you with the tools to separate database concerns from your main code base, you are tempted to write your own repositories and value objects for interacting with the storage layer. Why functionality implemented on Entity could not have been implemented for Model instead, I am not sure. Also, having access to the Model and ActiveModel type from Entity, would be at least easier, if you could just `pub use table::Entity as MyModelName`, and access `MyModelName::Model`, or `MyModelName::ActiveModel`, respectively, instead of sometimes using the module name, and then alternating between Entity and Model. It also kind of sucks, to have Entity as a name, as imho the more prevalent use of that word is in ECS systems, where it kind of means the same thing, but not really, while in the context of DDD Storage mechanisms, pardon my hot take, it is just a waste of good nomenclature. I have yet to find out, how i can move the automatically generated `entity` and `migration` folders to some subdirectory, like "crates", given putting them in the workspace is rather daunting, and is not a nice thing in general, imho, if not adjustable, but I am fairly sure that works out somehow. I also don't see the reason, why I would separate the migration app from the entity app, and not manage the whole thing in one layer instead, given I have to now look for version changes of sea-orm in 3 Cargo.tomls for 1 project. It seems to be an overabstraction, that is dictated by the tooling, mainly sea-orm-cli. However, this may be mitigated, or may be "accepted" by the user, as it is only a problem for a certain point of idealism, and might be easily defended in a discussion by anyone, who thinks that layers are things you cannot have enough of, and tons of Uncle Bob quotes. #### Source of Truth In Diesel my main issue was, that the source of truth was never clear for models, as the tools rewrote the schema.rs, while the migration could also be generated from changing the schema itself. Here SeaORM clearly shows 2 approaches, either migration first, or entity first. However, where Diesel seems to grow into a better workflow, and started to have autodetection of migration changes, even generating a migration (instead of an entity), from a current table seems unsupported in SeaORM, which means, either you accept writing migrations manually, and using that as your state of truth, or you lose all migration support. The entity first approach, is, as expected, not the main mindset. #### Ease of Use and the ORM Idealism Most ORMs come with some idealisms behind it. For example, if you look at SQLAlchemy in the Python world, sometimes you have the feeling, the authors never really wanted to write an ORM, as you need some SQL statements at least, usually to set up databases. I would say, Diesel is very similar in that mindset, as they only later introduced a programmatical migration language, and instead, expect you to write SQL in their tutorial. Django may have it's flaws, but the ORM of Django clearly has a source of truth: the Model. Changing the Model leads to auto-detected migrations, which means, you can automate checking model changes in the CI/CD. Also, the model language of django nearly covers all aspects of SQL, except the DEFAULT value, which seems to be hard to implement, as most ORMs don't support it. SeaORM seems like a weird cross-over. While I would have said, Diesel tries to go into the direction of full automation, and might one day finally solve it's source of truth issue, if it finally starts to introduce more options to define models, and throws away the idea of a common generated schema file, SeaORM seems not to bother, as probably most users of it work with the migration workflow in mind, and "there is is this a documented way to create entities in the database from code", even if that never really ties into their migration syntax. Both ORMs force you to put your models in certain places, even if SeaORM is more flexible if you just don't use sea-orm-cli, while diesel just won't run otherwise, and therefore expect the database layer to be some global service layer, which is fine in a microservice world, but kind of sucks in a modular monolith. So putting database models in various applications, like you might be used from Django, is not really a thing. It is better to just see your storage layer as a global database layer, and implement local value objects that implement From for the storage layer objects, and some repositories, that do all the ORM work behind the curtain. It's not like that is a bad thing, given this is also one of the downsides I witness in django projects, where models start to become swiss army knives around a domain topic. ## Accepting Fate #### Generating Entities from a Database sea-orm-cli generate entity -u postgresql://miniweb:miniweb@localhost:54321/miniweb -o entity/src --lib ## Admin Registry ### Dynamic index functions ### Dynamic Repo Traits ```rust trait AdminRepository { type Item; type List; fn get_item(&self, id: usize) -> Option; fn get_list(&self) -> Self::List; } struct AdminRegistry { repositories: HashMap>, } impl AdminRegistry { fn register(&mut self, name: &str, repository: R) { self.repositories.insert(name.to_string(), Box::new(repository)); } } ``` ```rust struct UserRepository; impl AdminRepository for UserRepository { type Item = User; type List = Vec; fn get_item(&self, id: usize) -> Option { // Retrieve a single user } fn get_list(&self) -> Vec { // Retrieve a list of users } } ``` ## Finding Names Ultimately the goal will be to rename even "app" and "model" as concepts later on - maybe. As I started by quickly using a django admin template with CSS to jump start the admin interface, with the hope to create something that is very much also compatible with existing django admin templates in projects, I ran quickly into naming maelstrom. #### Django-Admin: | key | description | | --- | ----------- | | app_label | This refers to the name of the Django application that contains the model. It's usually set to the name of the directory that holds the app. In Django's admin interface and internal workings, app_label is used to distinguish models from different apps that might have the same model name. | | model_name | This is the name of the model class in lowercase. Django uses model_name as a convenient way to refer to models, particularly in URL patterns and in the database layer, where the table name is typically derived from the app_label and model_name. | | object_name | object_name is the name of the model class itself, typically starting with a capital letter (following Python's class naming conventions). This is more often used in Django's internals and templates. | | verbose_name | This is a human-readable name for the model, which can be set in the model's Meta class. If not set explicitly, Django generates it automatically by converting the object_name from CamelCase to space-separated words. verbose_name is used in the Django admin interface and any place where a more readable model name is needed. | | verbose_name_plural | Similar to verbose_name, this is the plural form of the human-readable name of the model. It's used in the Django admin interface where a plural form of the model's name is appropriate, like in list views. | #### miniweb-admin: | key | description | | --- | ----------- | | app_key | refers to the lowercase slug representation of the app, which might be represented by an Arc in the future | | model_key | refers to the lowercase slug representation of the model, which might be represented by an Arc in the future | | app_name | represents the human readable representation of the app | | model_name | represents the human readable representation of the model | #### Replacing: | django-admin | miniweb-admin | explanation | | ------------ | ------------- | ----------- | | object_name\|lower | key, app_key, model_key | | | app_label | key, app_key | | ### Next Steps: - implement views for app - implement a static implementation for repository