This is more of a random theory question about pri...
# orm-help
j
This is more of a random theory question about prisma and where you have some logic live. I have a table
contacts
and a table
addresses
. Table
addresses
has a
contactId
and
isPrimary
. And
contacts
has a
primaryAddressId
in the table. If someone updates or adds an Address that
isPrimary = true
then it should set the contact’s
primaryAddressId
. So where would you as a prisma dev have this live? • Database Trigger • Middleware • Just my api code? • Somewhere else? This is actually a part of a bigger internal discuss I have been having and was curious this community’s thoughts
1
And if you are building a lot of business logic like this as middle ware, how to you manage it from getting out of hand?
We probably have 50-100 rules like this
m
i might use https://www.prisma.io/docs/concepts/components/prisma-client/middleware or add a worker
If someone updates or adds an Address that isPrimary = true
And if you are building a lot of business logic like this as middle ware, how to you manage it from getting out of hand?
We probably have 50-100 rules like this
from my experience, you could have a queue (SQS, Kafka …etc) you can push messages to the queue whenever you have logic to be done in the consumer
j
I would say that this is also data modelling problem, you have 2 locations where the primary address is contained, and they can be out of sync, which would go against your business rules. I have done a similar data modeling thing in the past, and personally, I would remove the
primaryAddressId
from the contacts table, and then add a partial unique constraint on
addresses(contactId) where isPrimary = true
- this would make it so that for each contactId in the addressed table, they can have only 1 row where isPrimary is true, but lots where isPrimary is false.
j
Yeah that makes sense I think it was just easier to query but that makes sense
j
down the road, if you have other categories of 'primary' say - "shipping", "billing" (2 very common ones) then in your addresses table you can just change the constraint to be `addresses(contactId, address_type) where isPrimary = true`` and you don't need to then add a
primaryShippingAddressId
to the customer etc.
j
Yeah that makes sense
@Muhannad do you have a queue service you have used that works with Vercel? I have always used larger self hosted or ones on the big providers like azure or aws
Looks like upstash is interesting
👍 1
@Jeremy Hinegardner oh yeah I get the issues with the model. It was a little contrived example, could of come up with something better
Now I am curious how I could do a relationship with the
@relation
attribute to have a 1:1 relationship with the primary address if the
isPrimary
was set true
n
Looks like upstash is interesting
Apart from upstash, you can also have a look at zeplo.io
j
@Jonathan Marbutt I don't think you can use the
@relation
attribute to load in the
primaryAddress
as a relation via the prisma schema since all the
@relation
items need to be fields on the model / in the database. And this may boil down to doing all those business rules in another layer of the application. You could do it with a middleware I think, and do a test on
params.odel == 'Contact' && params.select == 'select'
or something like that, although I probably wouldn't do that as that may not be an appropriate use case, since that would run on every db query (https://www.prisma.io/docs/concepts/components/prisma-client/middleware#performance-and-appropriate-use-cases) Since this is a business rule, that is getting some enforcement support from the db, along with the other 50-100 business rules associated with your models, this is where you would probably need a separate layer on your application that handles the business rules, and then those delegate to prisma for persistance. So you're probably not using the prisma models directly, but those are the core data structures for some other class. Or use custom models on top of the existing ones -- https://www.prisma.io/docs/concepts/components/prisma-client/custom-models#wrap-a-prisma-model-in-a-class - For this particular
primaryAddress
case - this is probably what I would do - inherit from the prisma class and extend it with a
primaryAddress
method -- this is just an example, not tested at all
Copy code
import { PrismaClient, Contact } from '@prisma/client'
class Contact {
    constructor(private readonly prismaContact: PrismaClient['contact']) {}

    function primaryAddress() {
        return this.prismaContact.contacts.find(contact => contact.isPrimary);
    }
}
Also - looks like prisma does not currently support partial indexes directly yet, you'd have to add that on as custom sql in the generated migration file itself https://github.com/prisma/prisma/issues/3076
j
Thanks for the detailed reply, That is very interesting.
I find it interesting because I am coming from the .net world and ms sql server. I have seen a lot of Postgres Devs talk about how much they put in their BEFORE and AFTER triggers whether it is for validation or some business/data integrity type rules. I have always handled this in my api layer and as an MS-SQL dev, I have avoided triggers at almost all costs. It seems more wide spread in the postgres world than others but I will say their trigger options are way more flexible than MS-SQL.
I have asked this same basic question on different groups to get different perspectives. Most people do it in the api layer, but some postgres devs want to have it completely covered and protected at the DB level which I get to some degree.
j
I'm definitely in the postgres camp, but I haven't used a lot of triggers unless it is for something that is specific like auditing (logging, automated history tables) or performance (automated summary counts of relations etc), or the triggers implement something that is completely outside the scope of the app itself. I do find that if you are implementing business rules in both the code layer with coordination at the database triggers/checks layers, that better be documented, since if that business rule changes, multiple areas of the application will need to be updated. It might also depend on implementation styles, so if the way that folks utilize the datastore is directly through db connectivity, then you probably need to implement business rules as triggers/constraints so that all applications follow the same rules and the rules are consistent across applications, but if all access to the data store is through a library, or through a network api of some type, then the business rules can be implemented in that library / microservice/ webapi etc. There are also infrastructure issues at play too, depending on your scale and your deployment, rolling out constraints and triggers to the db may be more complicated or require more coordination than rolling out an update to a microservice. It really doesn't matter to me so long things are consistent and people know how to change things and where those changes are going to be made.