Fork me on GitHub

How can I check whether the (optional) keys are within a set? e.g. valid keys are #{:foo :bar :qux} and would (s/valid? :myspec {:foo true})


ah! map-of is my friend


When using spec in the context of domain modelling, sometimes I see that some of the require I do are only for specs, as some entities would have properties from a different entity (sometimes to define relationships). This makes it very easy to end up with circular dependencies, is there any way on the namespace declaration to have only ns aliases, without requiring them to avoid circular dependencies?

Alex Miller (Clojure team)14:12:32

no, but you don't need to require them at all to use keywords with a certain namespace

Alex Miller (Clojure team)14:12:41

you just have to write the full namespace out w/o aliases


Ok, I thought about that, I wanted to know if there was any way to avoid having to write the full namespace


We’ve been using this pattern at Flexport: Not sure if it’s a good idea necessarily, but it works for us.


That looks a lot more work and complexity than a simple alias/`create-ns` like I pasted in the main channel. Could you explain what your macros are trying to do, beyond just the alias/`create-ns` call I showed?


Yeah, I think my def-synthetic-ns fn is overly complex, probably because of a combination of “my first macro” confusion and not noticing that create-ns returns the ns


not sure if I already mentioned this one to you last time we talked, but I tend to use a namespace called kws.clj (for "keywords") which contains only specs, and zero implementation details (other than very simple predicates) since kws definitionally depend on nothing, you can always depend on a "kws" ns, and no circular deps will arise


That could be useful, but I prefer to have different namespaces as I want to keep ns keywords on the namespace that contains function that operate on them, for now I am using keywords under namespaces that do not correspond to the files, like :product/id instead of ::product/id , even if I define it’s spec in ::product namespace. I prefer this to writing the full namespace, for that I’ll probably wait until there is a better way of doing so


I see! In case it wasn't clear, I actually use one kws ns per model (or 'module', depending on the chosen arch) e.g. product.clj + product/kws.clj


Oh ok, I thought you meant one big kws. Although this still does not solve interdependency between models where you want to reference each other through the specs, as you might need to import to define specs from another place. Image orders from a product, you might have ::product/id inside an order, and have ::orders referencing orders from product


that design sounds problematic per se tbh: the DB design might not be sound, and in any case the generative part of spec might choke on the mutual recursion


But databases have relationships of this kind, by the product_id of an order you are able to get all orders from a product (::orders) . It would be the case when you want to hydrate a one-to-many relationship, the many has id of the one , and the one has an array of the many


I am not very familiar with the generative part of spec, but a problem I can see in generative is that it wouldn’t generate the same :product/id for all orders when generating a set of orders, aside from that the specs per se are not recursive, no spec end up calling itself, but both reference things from the other


one-to-many is easily modeled as:

product/kws.clj # zero references to orders; since products exist independently from orders
order/kws.clj # references product specs; this is a simple (non-cyclic) dependency
and even a fancy many-to-many relationship can be modeled as:
product/kws.clj # zero references to orders
order/kws.clj # zero references to products
order_product/kws.clj # depends on orders and products


I wouldn't let a trivial concern like ids (normally ids have the same type across all SQL tables) introduce cyclical deps IMO that's true regardless of the way you decide to use spec


I still think could be useful to have some product info on the order itself (without directly referencing a product). Maybe this scenario is not the ideal one for this use case, but I still think you might want to reference inside your schema specs from other ns, I don’t think it’s bad to have cyclical namespaced keywords inside specs, as it’s not a dependency itself, and allows a lot of flexibility for how you retrieve your domain entities. In my case the spec are not directly translated to database tables or columns (at least not a database I control), and sometimes properties from a parent entity are hydrated directly on the child entity, not always referencing by id


> I wouldn’t let a trivial concern like ids (normally ids have the same type across all SQL tables) introduce cyclical deps Namespaced keywords do not need a require, so it’s not strictly a cyclical dep, that’s only for aliasing them, and I think it is better to have the flexibility of being able to do that (or at least as it is now just using full namespace)


Could be, I'd have to check it out carefully Still, keep in mind that specs are composable. so you can have non-cyclical, discrete product and order specs, and then completely aside, a hydrated-order spec that composes both. keeps things non-cyclical, without compromising DRY either :)


also keep in mind that at write time, order should only have thin references to products. so a full-blown spec could be an impediment, as it could require more product keys to be present. the hypothetical hydrated-order spec keeps read/write concerns separate


schema/select allow to handle those contextual dependencies

👍 4

Basically I am going for these specs with schema like “This is everything an order might contain”, then by context I could select some properties


yeah. it's in the radar for spec 2 as you probably know


must be said, considering graphql-style responses, a monolithic spec can end up being problematic. there can be N "read models" of a given thing


Yeah, as this is a personal project I am already using it, so my concern was on that context

💯 4

> must be said, considering graphql-style responses, a monolithic spec can end up being problematic. there can be N “read models” of a given thing I am not familiar with graphql, what do you mean N read models?


there are N possible schemas ("models") in which a client might want a response e.g. [[1 2] [3 4]] or {1 2 3 4} (same data, different representation) a monolithic spec cannot reasonably define all schemas that client could request


Why not? And what do you mean by monolithic schema?


monolithic: a single spec that can be used for describing both reads and writes, relying on select for flexiblity


problem with that: different clients (e.g. web, android, ios) might want different data shapes, pagination, camelCase, referenced models, etc probably a single spec defining all possible combinations of those would not be maintainable


What would be best in that case, one per read model and one per write?


I think I would have 1 spec for the read you retrieve from the DB, and then map it to whatever the client expects, probably not event having a schema for the specific data shape of the client, or if you want, then a spec for each of those too


It’s far from my use case though


> What would be best in that case, one per read model and one per write? at work I advocate the following: * there's one write model (since there should be only 1 acceptable way of persisting things) * by default, that also serves as the read model (since one always can read a subset of what he wrote) * there also can be N additional read models, as your needs grow so, rule of thumb is 1:N


> It’s far from my use case though (edited) yeah I don't even particularly like graphql, and in a personal project you likely won't have 10 different clients ;p but I like finding patterns that scale, that are universal. that way I don't have to worry later, or have different patterns per-project


> or have different patterns per-project I disagree here, depending on the scope of the project this could make sense


morning :) sure thing. Personally I enjoy having a minimal amount of patterns in my head. The tradeoff being that sometimes 'scalable' solutions can certainly feel as overkill


Morning 👋 I think though that scalable could mean different things on different projects, to justify different patterns to achieve such scalability

👍 4

You can also do this to get a local alias without needing the full namespace to actually exist as code:

(alias 'm  (create-ns 'ws.domain.member))

👍 4

Thanks! This looks really good and something that solves my use case 🙂


Then ::m/country will expand to :ws.domain.member/country for the spec name.


(in this particular case, that ns does exist, but we don't need to require it everywhere we use the specs)