Prisma feel like it leads to bad code design patterns
I’ve been learning and working with Prisma as I rewrite a project of mine in Typescript and one thing really stood out to me with it, it’s really easy to create complex queries and create / modify data from multiple places
Quick example, users can change settings in my project, that has the potential to happen from multiple places and if I made each one of those it’s own Prisma call with update, then if I ever wanted to add analytics to settings changes I’d have to go to each place and put an event listener
The way I’ve set up Prisma in my project is similar to how Discord JS handles their interactions, with managers and structures
If someone wants to update a user setting, that has to go through the user setting manager, this way there’s one single edit function so if I ever go to dispatch events on user settings change, I can be sure I’ll capture everything
Along with that, setting it up this way allows me to enforce rules on the codebase, for example no creating of foreign keys in Prisma queries, if you want to make an object you have to go through the manager to ensure all relevant events are fired
The reason I ask this is because I’m writing a lot of boilerplate on top of Prisma now to try to make in this style, while I’m somewhat happy with where I’m at with it, it also feels slightly wrong. I’m a bit torn on if this is dramatically over complicating things or a good approach for scaling my application.
I think for my project, my approach makes sense, with the trade off being the complexity of Prisma for a setup that I know will be scalable
How do you approach Prisma in your projects, do you have help functions, something more complex, or just raw queries throughout the code?
Here’s some code to help understand what I’m talking about, it’s a bit rough around the edges, just playing with ideas for it atm
Abstract classes:
Manager:
https://github.com/AnswerOverflow/AnswerOverflow/blob/Turbo-Repo-Base/packages/core/src/managers/manager.ts
Structure:
https://github.com/AnswerOverflow/AnswerOverflow/blob/Turbo-Repo-Base/packages/core/src/structures/base.ts
Implementation:
Channel settings manager:
https://github.com/AnswerOverflow/AnswerOverflow/blob/Turbo-Repo-Base/packages/core/src/managers/channel-settings/channel-settings-manager.ts
Channel settings structure:
https://github.com/AnswerOverflow/AnswerOverflow/blob/Turbo-Repo-Base/packages/core/src/structures/channel-settings.ts
33 Replies
I realize that’s a long wall of text to quick TL;DR of the concerns:
1. Queries get scattered throughout the codebase
2. The complexity of queries allows for data to be created / destroyed / updated in multiple locations resulting in possibly missing events
3. Wrapping Prisma queries results in a bunch of boilerplate and loses the power that Prisma offers
check remix planner by Ryan Florence
Unknown User•3y ago
Message Not Public
Sign In & Join Server To View
This is interesting, it looks like a query per function which could be a good approach
Yeah I suppose with Prisma it's up to you to use the power to choose your own adventure with it, in my case, I'm choosing to trade off the power of having a bunch of complex queries in exchange for having more control over how data flows in my application as that's what is important to me - thanks for your input on it
In other things I'm doing, now using custom types made out of my Prisma types with Pick & Partial to ensure that callers can't modify certain fields
This setup feels both pretty wrong and right, for my project there's likely going to be a lot of contributors who aren't familiar with the codebase, so this tradeoff again increases safety imo
The only drawback is its so much code to write for the managers, it's helped a bit with generics & inheritance but still
it's always a game of whack-a-mole... you reduce the complexity for reasons here, then suddenly some new complexity pops up there...
the right answer depends a lot on scale
scale of data, scale of concurrency, scale of number of people working on it, scale of organizational stupidity (that's a big one usually)
hey , I checked out your code
just some first impressions / food for thought
you are doing a kind-of mvc-ish approach
I'd consider a more flat structure with more co-location, organized around entities / features, instead of (deeply nested) organization around functions
so instead of having
core/src/managers/channel-settings/channel-settings-manager.ts
you could have core/src
so far so good, because you're using turbo repo and packages, etc
and then features/channels
Thanks for the feedback on that I appreciate it, do you have a repo you recommend I check out as a reference?
I'm playing around with the structure of it atm,
The main thing I like about the current structure is it lets me quickly find and view a function as I can do Ctrl + P in VSCode and open the specific file for say creating a user, making my flow to editing code be: realize a function need to be changed->open file and edit instead of realize change is needed->open file-> scroll to find function->edit
Unknown User•3y ago
Message Not Public
Sign In & Join Server To View
That’s a pretty interesting approach, took me quite a few times of reading over your message to understand it 😅
These concepts seem like repeatable problems that would show up in multiple different codebases, I’m surprised this isn’t a more solved issue unless I’m missing something
you could have the same with a more flat structure
I also like the (i'm on a mac) CMD + T shortcut, which jumps right into the function definition / type definition, etc
With a flat structure, does it result in files ballooning in line count? That's a slight concern of mine as when you load up a file thats >500 lines it takes a long time to digest it
Quick example, I currently have this as it's own file
get.ts
In a flat structure, would I be able to make get.ts, update.ts, delete.ts... etc or would I just make a crud.ts file and put it all in there?
I could do something maybe like this also
if you've got a good example repo I'd love to take a look at an example to get a better idea of the structure
Unknown User•3y ago
Message Not Public
Sign In & Join Server To View
I would organize it around entities, so
user.ts
--> would have all of the crud operations and if you want to have these abstract classes (I would probably avoid them and lean towards a functional style, but that's a different conversation), I would simply put those inside a utils.ts
if that gets out of hand, then you can introduce another layer, and have the entities as folders, inside said foders maybe start out with users/crud.ts ... users/utils.ts
then if that's not granular enough, you can break it down even more, also what @natefrosty21 said about service.ts ... api.ts
and so on can make sense
There's really no right or wrong answer, you should experiment with this, and your approach should change and evolve with your project.
It is a good strategy to start as flat as you possibly can and add abstractions very conservatively. The problem is that once you committed to an abstraction, it's generally harder to make changes.
To me it seems like in your project you barely have any code yet, and are worrying about how to organize that code.
It doesn't really matter 🙂 Grow the project and if you run into some noticeable / measurable problem, THEN worry about what abstractions you have to use.
this looks really interesting, I don't see how it would help until a fairly large scale, but maybe I just need to play around with it a bit
do you have any hands on experience with it?I’m sort of at that stage now 😅 - I’m refactoring an existing project with like 20-25k lines of Python and trying to avoid making the same mistakes that I did on that project
I found that as the codebase scaled it became harder to do things like add analytics, ensure that foregin keys properly exist before creating objects, reuse code, etc
Trying to do this version of it a bit differently where I focus more on design patterns and good architecture at the start as last time not doing that really hurt my progress
That’s kinda why I’m so focused on these abstractions now as I’m trying to solve the problems I ran into on my last codebase and I think the best way to solve it is through this manager path, just a matter of figuring out how to put it together
I’ve never seen this before that’s pretty interesting, the cache is something to just have in memory as the code is also being used on a discord bot, so if it get spammed with requests the goal is to not refetch that data if needed
ah, gotcha, makes sense then
I think I'm sort of close to the design I want for it, I've got an idea on how to cut down on a lot of the boilerplate, I'll post that in this thread when it's ready
If you come from the Python world (I do too btw), then there are a few common patterns that might be a good idea to "unlearn"
When python and Django had it's golden era, in general OOP was much more the norm than today.
So you obviously represented your data as classes, and then it just made sense to encapsulate your transformations as instance / class methods with your data
then like you set up your database connection as a singleton class (and you remember that from school anyways)
and it all seemed to just fit nicely togetehr
but, it just doesn't scale well
it really does feel like it should work, the abstractions are very pretty, elegant
but it just doesn't
SO you got used to solving everything with like abstract classes, decorators
Base classes... factories.... design pattern this, design pattern that
A much better model is to think of your application as a series of transformations and side-effects on your data.
And you pipe these together, one after the other.
You can think of stuff like "adding analytics" as just another step in the pipeline
A specific "transformation", where you don't actually change anything on the data, you fire some side-effects
With your current implementation, you have:
1) a base class
https://github.com/AnswerOverflow/AnswerOverflow/blob/Turbo-Repo-Base/packages/core/src/structures/base.ts
2) an abstract class for managers that extends the base class
https://github.com/AnswerOverflow/AnswerOverflow/blob/Turbo-Repo-Base/packages/core/src/managers/manager.ts
3) an entity specific manager that implements the abstract class
https://github.com/AnswerOverflow/AnswerOverflow/blob/Turbo-Repo-Base/packages/core/src/managers/users/user-manager.ts
Contrast this to what you are doing, which is:
1) crud
2) caching for the R in crud
GitHub
AnswerOverflow/base.ts at Turbo-Repo-Base · AnswerOverflow/AnswerOv...
Contribute to AnswerOverflow/AnswerOverflow development by creating an account on GitHub.
GitHub
AnswerOverflow/manager.ts at Turbo-Repo-Base · AnswerOverflow/Answe...
Contribute to AnswerOverflow/AnswerOverflow development by creating an account on GitHub.
GitHub
AnswerOverflow/user-manager.ts at Turbo-Repo-Base · AnswerOverflow/...
Contribute to AnswerOverflow/AnswerOverflow development by creating an account on GitHub.
I just don't see the value of all these classes... what are they for? What problems do they solve?
You already have a good abstraction for the CRUD stuff --> Prisma!
You want to have some sort of caching for the R part, why not just create a utility function, something like
read(cache: Cache, query: Query): Result
and... that's about it
If you need to add some monitoring / logging / analytics, then create a side-effect for it mySideEffect(data: Data): Data
and add it as an extra step in your pipe
It makes sense to reuse your queries and maybe collect them in one place (I guess this is one problem the manager class sort of solves)
but why not just do something like this?
and you can just have a convention of always starting with CRUD and then the more exotic ones
on a side note, why have the caching layer in front of the database at all?
I'm a bit sceptical you'd get measurable benefits
if you move it up however to the network request
you get a very noticable improvementThank you for the feedback on it, I really appreciate the time you’re putting in to help me with this
Solving everything with abstract classes, decorators, etcYeah I’m looking at this code again now and it’s just too complex for what it’s doing, I agree with that - for making a manger there’s too much boilerplate you have to write and it’s hard to conceptualize
Transformations and side effectsSo on this note, rather than directly calling side effects I was planning on using the observer pattern in the manager, so when say an edit comes in, it’d dispatch an event for others to use The reasoning on this is I’m wanting to separate that analytics logic from the database calls, this way since it’s an open source project, if someone wants to disable analytics all that needs to happen is the analytics never subscribed to listening for the event
Already have a good abstraction for CRUDPrisma is pretty nice, but the concern I guess is it’s almost too powerful The goal of this project I’m making is to be accessible to a bunch of people as an open source project and if I expose the raw Prisma args then new devs to the project, or me being forgetful, may end up changing fields that really shouldn’t be i.e a created at / user id field Along with that I’m wanting to prevent creation of classes from nested Prisma queries again to make sure all the side effects fire properly Also some of my data types need to be transformed before being given back to the caller, for example I want to turn a number for settings into a bitfield so the utility functions are needed for that
why have an in memory cache?Yeah I’m getting rid of that now, trying to focus on getting this manager part right and then may revisit it but the main reasoning on it is it’s easy enough to have and isn’t too complex to implement I saw other open source projects have a similar in memory cache so I figured it’d be helpful I’ll play around with the concepts you suggested and see what I can come up with, I think I may end up doing a mix of the two
in memory cache is good, I'd just solve the network level caching first, and then focus on the db level caching
better bang for buck
Main thing was in memory caching just seemed easiest to add on, im assuming something for network level caching would be like Redis or some other service?
caching is a big topic
people have been posting from these docs, so I've been browsing the past half hour
lookie what I found!
Prisma's Data Guide
Database caching: Overview, types, strategies and their benefits.
An introduction to database caching strategies and their benefits.
you can achieve what the observer pattern is for via functional patterns
you don't have to go OOP for that
Functional reactive programming
Functional reactive programming (FRP) is a programming paradigm for reactive programming (asynchronous dataflow programming) using the building blocks of functional programming (e.g. map, reduce, filter). FRP has been used for programming graphical user interfaces (GUIs), robotics, games, and music, aiming to simplify these problems by explicitl...
trying to find a good example for you, but my google-foo is not up to it somehow, anyways I'd look around here
https://github.com/ReactiveX/rxjs
GitHub
GitHub - ReactiveX/rxjs: A reactive programming library for JavaScript
A reactive programming library for JavaScript. Contribute to ReactiveX/rxjs development by creating an account on GitHub.
And in regards to controlling / limiting Prisma, well simply wrapping the prisma queries into functions seem to solve that, no?
E.g.
and finally for the data transformation stuff, that's where FP shines!
just add an extra transformation step, something like
I'm writing pseudo code, but hope it makes sense
anyways, go build cool sh$t champ!
was just trying to show a different perspective ❤️
I think I'll shut up now 😄
actually, I kind of got interested, you mentioned this being an opensource project
and it's a cool idea to open up discord to google indexing!
wanna move this conversation to github and collab on this? 🙂
Sure I'd be happy to! I was planning on writing the base of it first and then opening it up for collaboration but honestly the core parts of it are the most important part to get right so more help is welcome
coolio!
I'm going hiking on the weekend, but lets talk more next week 🖖
I'll friend you in the meantime if you don't mind as a bookmark of sorts
sounds good! in the meantime I'll set up the GitHub project to be a bit more collaborator friendly
the good news is the project should just work off the bat if you've got VSCode and docker installed
Unknown User•3y ago
Message Not Public
Sign In & Join Server To View