This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-11-06
Channels
- # announcements (5)
- # asami (4)
- # babashka (27)
- # beginners (1)
- # calva (4)
- # cider (64)
- # clj-kondo (7)
- # clojure (7)
- # clojure-brasil (3)
- # clojure-europe (41)
- # clojure-france (2)
- # clojure-norway (101)
- # clojure-uk (5)
- # clojurescript (19)
- # cursive (3)
- # datahike (15)
- # datomic (15)
- # events (2)
- # honeysql (11)
- # hyperfiddle (27)
- # introduce-yourself (2)
- # jobs-rus (1)
- # leiningen (8)
- # london-clojurians (1)
- # lsp (175)
- # off-topic (52)
- # overtone (10)
- # portal (15)
- # re-frame (7)
- # reagent (1)
- # releases (1)
- # remote-jobs (2)
- # shadow-cljs (15)
- # sql (5)
Greetings Datomicians, Having just reviewed a previous datomic schema I wrote some years back, to now freshly iterate on the design, I have the following query. The best practices listed in the Pro docs are great and fundamental, dare I say philosophical (which is great), but they do leave actual specifics to the reader. So I'm looking at what should happen upon the bash entry point which will be used to transact schema definitions into datomic. Firstly I'm pretty clear that schema definitions should be split across multiple files, probably .edn. Then this 'migrate' command would read the directory containing these and ensure all have been transacted into the database. Small point perhaps but while the standard design of these in sql (at least, following the rails pattern, which I know is suspect according to the cognitect/datomic philosophy which eschews breakage) stores something like :migration/name in the database so that each database instance holds a record of what migrations have been run, and by implication which still need to be run, I realised that in datomic's case, if we transact data which already exists, datomic is fine with that, as if to simply reply "I have nothing to add" (Charlie Munger in there perhaps). So it seems we don't need to store :migration/name at all. So I'm just musing on this a little and whether or not it'd still be good to add that, even though it appears we may be fine without it. Subtleties... connoisseurship... : )
but many migration systems allow a migration to be code as well, for example a migration that adds a new attribute then runs some code to fill in the value for previously existing entities… you probably wouldn’t want to run these again every time, simply to avoid doing unnecessary work
i can't think of a case where i have used or would use a migration to execute code (rather than just schema). a seeder is a different thing in my experience, and is usually part of tests, as opposed to migrations. but i guess your point could apply to a production db where you're doing 'database refactoring' kind of thing, that said the datomic best practices docs make clear they advise we don't follow that that philosophy. i noticed datomic does make it a lot easier than in sql to follow those principles. we can use aliases for starters, to achieve 'renaming', without actually needing to rename.
@U013RDDF9HT, I wrote https://github.com/recbus/caribou to work roughly as you have described. It stores completed migrations in the database and it currently used by my company for schema migrations and seeding of production, development and test databases. It also supports arbitrary code migrations (very much for the use case described by @U11SJ6Q0K), but with the obvious restriction that transactional guarantees, while not completely absent, are limited. The primary differentiator of caribou w.r.t. to other migration tools is treatment of the migrations as a dependency tree instead of an ordered list. This can buy some flexibility in deploys and parallel development.
Hi all! I'm trying to create a Datomic model to describe an item with text descriptions in multiple languages. The first thing that came to my mind was to use a tuple of ["language" "description"], with cardinality many; my expectation was something like {:item/id "123456", :item/description ["en" "Copper Pipe"], :item/description ["fr" "Tuyau en cuivre"]}. However, with this setup, all transactions fail with "Invalid tuple value" - even a single-description test item that works with cardinality one. I have experimented further, including different TupleTypes, and found increasingly weird behaviour that is only confusing me further. Please can anyone answer one/both of the below? a) If there's nothing egregiously bad about my many-tuples approach, what have I done wrong? b) If it is egregiously bad, what is the "right" way to do it in Datomic? I thought of creating a separate entity like {:item-desc/item-id, :item-desc/lang, :item-desc/text}, but in my mind it's clearly data about the "Item" entity; having a standalone "Description" entity is a solution I've seen elsewhere, but never felt right...
Could this be from the map syntax thinking these tuples are lookup refs? Have you tried transacting with [:db/add e :item/description v]
?
Hi @U09R86PA4 - got it in one! Being explicit results in the exact desired behaviour (and the ambiguity explains some of the unusual behaviour I saw as well). Thanks also for your pointer about char limits - the system I'm replacing had a 20-char limit for descriptions so fine for this case, but very useful info for later. How would you have approached this problem?
not sure why you're using :item, why not just use :description.english :description.french. That way you only need one attribute for each lang. Tuples are to do with uniqueness as i recall and i don't think you want that at all. remember datomic is an attribute database. you may still be thinking in terms of tables which is easy to do at the start. what do you need ":item" for? it's a very generic name. if you do have a more specific name, you can also use namespaced attrs like i mentioned above.
to summarise, namespaced attributes like that is the way i would personally implement "tables". but in this case a description with a language, fr, en etc., doesn't even need to be assigned to a 'table', and we use the namespace for a hierarchical description of a generic attribute, applicable to any entity "type".
There are three approaches with different trade offs: entity, tuple, and attribute. An important consideration is whether language should be data (high cardinality, unpredictable, opaque, or user-sourced) or code/schema (the opposite). Entities are more ergonomic and extensible (eg metadata about the translation perhaps), but involve more joins to read vs tuples. Attrs per language involve fewer still, but now you must manage language axis as schema/code instead of data. You can also combine these approaches. eg many translation systems have a symbol representing the text, and this symbol is parameterized by language (sometimes other things) on lookup and display. You could do the same with translation entities where the attribute that has translations has a symbolic or reference value used to get the available translations from a pool of entities (maybe one per symbol+language, maybe one per symbol with an attr per language). This also lets you ensure uniqueness of languages per translation more easily.
There’s no one way to represent this—it depends on what read and write operations you expect to perform and how you expect your system to grow conceptually and operationally
Hi @U013RDDF9HT - :item is purposefully generic, as it’s meant to represent a superset of products, subassemblies, and components/raw materials; the common alternatives are :part (seems unsuitable for a product) and :material (which, internally, is often conflated with a physical material i.e. different grades of steel). My intention with a tuple was to keep the language codes as data rather than schema, so that new languages don’t require schema changes. However, I’m starting to think I’ve misunderstood the intention of tuples in Datomic - while composite tuples for identity are presented as one use case, it seems to be the only accepted use case (or at least the only one I’ve seen properly documented). If there are implementation details that might be problematic then I’ll steer clear. Hi @U09R86PA4, language codes will be a dropdown using ISO639 so hopefully “data made predictable”. In your later example, are you referring to something like a UI element showing “123456 - %n” where %n is replaced at runtime by the description in the user’s local language? That’s the kind of approach I’m looking to take eventually. It sounds like you’re describing something like my entity with a composite tuple of id and language, which would ensure uniqueness per language, so I’ll explore this as an alternative. Thanks both for your advice!
As a note for anyone else having the same specific issue I started with - it appears that when transacting tuples using the map syntax, an additional set of square brackets is required, i.e. {:item/id “123456” :item/name [[“en” “Copper Pipe”]]}. This results in the expected datoms and query behaviour. This seems neater than having to use :db/add for tuples and map syntax elsewhere.