Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

At first, I thought; /hey pretty cool feature!/

But after contemplating it, is this really necessary? I fear for putting business logic and meanings into the wrong the layer; There are use and abuse, and my consideration fears the latter.



Why do you think placing business logic into the database system is a layering violation?

Just because most developers use their DB as a dumb store doesn't mean it needs to be. There are also plenty of successful software systems that place the majority of their business logic and use a generic programming language and runtime only for the presentation layer.

If you're comfortable fully exploiting the capabilities of your DB, then the intelligent combination of a relational model with custom data types, constraints, triggers, views and stored procedures can make the DB the perfect place to implement business logic.


I love what you're saying, but running a normal modern development and deployment cycle on code stored in databases is hell.

Until db vendors start taking developer happiness seriously, stored procedures and triggers are a total non starter for any serious work.

I mean, in most databases you can't even rename a column without causing an enormous blocking migration. What? Why can't this happen in the background? Why can't I have column aliases? The very basics of developer happiness aren't covered, let alone the harder bits, like versioning stored procedures, switching schema and code when switching to a new git branch, and so on.

(EDIT: of course there are open source tools that help with all of the above, but they're all swimming upstream, fighting a database that simply can't imagine change, and are usually terribly leaky abstractions as a result)


> I love what you're saying, but running a normal modern development and deployment cycle on code stored in databases is hell.

Can you be more specific? What exactly are you missing?

As long as you put your code and data into separate schemes and follow good technical practices, it shouldn't be too different from other technologies.

> I mean, in most databases you can't even rename a column without causing an enormous blocking migration.

Changing a column name is just a metadata change, so it shouldn't take too long in Postgres.

> The very basics of developer happiness aren't covered, let alone the harder bits, like versioning stored procedures, switching schema and code when switching to a new git branch, and so on.

Does Tomcat or any other application server version your WAR files for you or does it manage git for you?


Any DB that requires moving large amounts of data on disk to do a column rename sounds pretty lame.

I’d expect most DBs to be like this: https://dba.stackexchange.com/questions/189794/performance-i...

A rename should be just a metadata change.


unless your using jsonb to store data ;)


Also in my experience DB Schemas often (but not always) outlast logic implemented in applications. The more the business assumptions reside in the database, the easier it becomes to rewrite applications on top of it later.


Minimizing the amount of busines logic in the dB is exactly what makes the dB structures long lasting.


> Minimizing the amount of busines logic in the dB is exactly what makes the dB structures long lasting.

The trick is to keep declarative business logic in the database layer, and imperative business logic in the application layer.

This allows a large team of developers to move quickly without breaking things.


Very Interesting. Never heard this before.

Can you give some examples of what would constitute declarative business logic vs imperative business logic?


Declarative programming expresses the logic of a computation without describing its control flow.

This new feature in PostgreSQL is a great example of that: generated columns (declarative logic) were introduced to reduce the need for triggers (imperative logic).

In SQL, declarative logic consists of constraints, indexes, views, and prepared statements. They can significantly increase the efficiency and reliability of the entire system. Imperative logic is mostly triggers and stored procedures, both of which can become hard to maintain and scale.


How does one logically follows from the other?


that's very well put. Thx!


Clearly the feature should not be abused, but calculated columns are great for stuff that's obvious and always true (i.e. independent from applications), e.g. a trade value computed from a trade unit price and a trade quantity.

This way I can select the top N trades for a given key without having to do the computation in the application, or storing redundant information in the DB.


Not postgresql but I saw a start date and an age in days columns in MS SQL server. The age gets updated daily. It didn't sound right to me. I'm pretty sure I'd fail my database class in college if I did that. What is different in real life and why didn't they teach me this in college?


I obviously can't answer for this particular case, but my first thought on why I would consider doing such a thing is if I had an app in which number of days old was something that had to be queried, displayed, and/or used in other functions/queries a massive number of times per day in the course of normal application usage. If the application had low usage, or number of days old was infrequently queried/displayed/used, I wouldn't consider it. The moment I found that significant time & resources were spent calculating the value in normal/regular usage, I'd start looking at ways to reduce that time & resource usage. How to go about it varies, but the win of that value being immediately available without computation could mean a lot to an app/business and its users.


There's no clear separation in databases between data definition and application logic. And this separation is very useful. You can change application logic very easily. Just stop old application and start new. You can use multiple application instances to balance load in many cases. You can often rollback bad application update. There's absolutely no problem to use miriads of development tools from Git to CI systems.

Changing database schema is a big deal. It might take a lot of time or it must be done with great caution to keep database online. It's hard to properly version it and it's often hard or just impossible to roll back bad update.

Generally database is state and application is stateless. You can couple it, but decoupling works better.


Incorrect separation of data from logic is a human problem, not a Postgres problem. Put your logic in one schema[1], let's call it code schema and your data in another schema, the data schema. There you have your separation. Now you can:

* Change your application logic very easily.

* Use a transaction to deploy new code! Zero downtime! [2]

* Use read replicas to balance load in many cases.

* Rollback bad application updates

* Test anything [3]

* Use miriads of development tools from Git to CI systems.

* Use row level security, so that every user can only see his own data [4]

* Only allow applications to call your code schema, never let them touch your data directly.

[1] https://www.postgresql.org/docs/current/ddl-schemas.html

[2] https://wiki.postgresql.org/wiki/Transactional_DDL_in_Postgr...

[3] https://pgtap.org/

[4] https://www.postgresql.org/docs/current/ddl-rowsecurity.html


Agreed. After all, if you wanted to keep all "business logic" out of the db you wouldn't even use foreign key constraints.


You probably wouldn't use multiple tables or multiple columns either and just have a single table that stores document-like rows... which vaguely reminds me of something.


PostgreSQL is a pretty good document store, and when you need it you have the relational model available and integrated.


Foreign key constraints are not business logic, they're part of a sound and sane database architecture (just like basic indices on heavily-queried columns). For an overwhelming majority of use cases, tables should have them; one needs a very good reason why a table shouldn't have them.


Or arguably data types.


The last time I tried to do that, I struggled a lot with error handling. The errors your database give you for schema violations aren’t really user friendly, so I have to convert them to proper errors (you must not enter negative amounts). When using SQLite there didn’t seem a way to know what column cast the error without parsing the string. That often lead that I had to implement the business logic twice. One check for in the code for the error handling and another one in the database. That was also often quite inefficient and prone to race conditions (if using improper isolation). For example if the table has two unique fields, I cannot just check for a unique constraint violation because it doesn’t tell me the column in the error data structure.

Looking at the Postgres driver it seems easier but is there any good tutorial on how to do error handling properly for databases?


Scaling and replication! It is easier to scale horizontally the application layer than the db layer. Application/code has better debugging tools, IDE.


All sorts of things are possible but it misses a fantastic opportunity to compartmentalise the data away from the implementation. If it doesn't make sense to compartmentalise data and logic, why compartmentalise anywhere? Do the whole project in one big file. Of all the surprises a project is going to face 'oh, this data is useful for [new thing]' is one of the most likely. And everyone expects to find a boundary drawn there because it is such an obvious place to draw one; so it saves on confusion.

Putting complex business logic in the database is opening up all sorts of interesting new ways for data to be unavailable, corrupted or complicated to access. It is easy to imagine it working out well for simple requirements where there just needs to be something that works.

PostgreSQL is a piece of software that takes data and enforces the relational data model on it. Great idea. But the relational model of data is really only tuned to relational algebra. Put complex logic in there and all that is really being accomplished is now you can't migrate away from PostgreSQL. Relational databases already have great integration with every other programming language in current use.


> All sorts of things are possible but it misses a fantastic opportunity to compartmentalise the data away from the implementation.

That's what schemas are for. You have a schema for your code and a schema for your data. You can redeploy the code scheme independently of the data scheme and set up permissions so that higher layers can only use objects from the code scheme and never touch the data.

> Put complex logic in there and all that is really being accomplished is now you can't migrate away from PostgreSQL.

Say you wrote your code in PHP, now you want to migrate to Ruby or NodeJS. You can't, you have to rewrite everything. How often do you plan to migrate to another database? In my experience this almost never happens in reality, but the layers above come and go.


> You can redeploy the code scheme independently of the data scheme and set up permissions so that higher layers can only use objects from the code scheme and never touch the data.

That all just sounds a touch complicated compared to data in database, code somewhere else (like git). I'm circling back to the same point in a couple of different ways, but pgSQL specialises in the relational model of data. That isn't a great data model for code and there are already better ways to manage code than shoehorning it into the database. Its cool that it is possible, and I'm not saying that someone who does is making a mistake. But I also don't think they are gaining an advantage and there is a really easy opportunity to separate out where bugs can occur from where data lives.

> How often do you plan to migrate to another database? In my experience this almost never happens in reality, but the layers above come and go.

If you are using a database for its advanced general purpose programming capabilities? The chances probably start to get more likely.

Databases that are a store of data don't need to change because they already do their one job (disk <-> relational model translation) really well. If they are pressured to do 2 or 3 tasks like business logic then suddenly it is a lot more likely that there will be pressure to swap databases.

If I were using SQLite and someone wants to do fancy triggers then maybe I need to swap to PostgreSQL. Avoiding that sort of decision making is a great reason to seal the data completely away from the code.


> That all just sounds a touch complicated compared to data in database, code somewhere else (like git).

You use Postgres as a deployment target and not as a replacement for git. It's not complicated at all. You even get features like transactional deployments and the ability to prohibit applications from directly touching the data.

> pgSQL specialises in the relational model of data

The relational model is SQL:92, Postgres does much more than that. Postgres has JSON support, recursive CTEs, Row Level Security, and Window functions that would require dozens of lines of procedural code to do what can be done with a single OVER in its SELECT clause.

> If I were using SQLite and someone wants to do fancy triggers then maybe I need to swap to PostgreSQL.

If you want to put your business logic into an RDBMS, you wouldn't be using an embedded DB anyway, but rather Oracle, Postgres, SQL Server or DB2, which are designed for this type of architecture.


This thinking really intrigues me, as it seems like it fell out of fashion (at least for greenfield projects), but it might have a come back.

Could you share a few cases where it is better to use a custom type as opposed to a relation? The only things I can think of are generic things like uuid. Would there be a need to create an Employee type vs an Employee relation?

Also, how is the experience with using Python for stored procedures? One reason they are not used is that the language pl/sql is non-familiar to most. If anyone has any experience with that, could you share some of your thoughts?


It could just be something more pedestrian like not yet being able to handle database changes well for deployments. Things like blue/green, canary, reverting, etc. It's a bit of work to get that functioning well.


> But after contemplating it, is this really necessary?

No, you could always do the materialized equivalent via triggers, so it's not necessary for correctness. And since this feature is limited to the materialized form, too, it doesn't offer much change other than simpler expression of what is going on (which, to be fair, is a big win.)

> I fear for putting business logic and meanings into the wrong the layer

If you don't have some mechanism for calculated columns, you are forced to put domain logic that belongs in the DB in the app layer, which is especially problematic in the (increasingly out of fashion, apparently) circumstance where there are multiple consumers of a DB, as it promotes inconsistency.


This is also much faster than the equivalent using a PL/pgSQL trigger.


If your database contains objects like "customer ID", "address" and the like, you already have business considerations (if not logic) inside it. In fact, businesses using databases for business stuff is probably 95% of DB use cases.

If it's something that's not application-specific and fits nicely into the query language (like in the example given) it makes sense to have it inside the DB, because for queries involving sorting, limiting, etc. you can do all that stuff inside the DB and return the few values that interest you, instead of transferring all the data and having to write code to do that inside your app.


Arguably, a robust RDBMS with user defined functions (such as PostgreSQL) are the ideal place for core domain semantics.

What is missing is a modern tool chain that addresses the painpoints of such a development model.


It's super helpful for database migrations.

Say you suddenly don't have data for a column anymore, because the API that you read from has stopped providing it. Now you can add a computed column that uses a heuristic as an interim until you have time to remove all the reads from that column.

Or say that you have an object with a separate display name, and product management decides that the display name now can't be chosen freely anymore, but must be generated from name and title. A computed column can do that trick pretty well.


As an analyst, either for reporting or machine learning tasks, it scratches my itch.

I'm often creating wide, flat views with extended attributes. Many columns are nearly identical, for example, year as an integer, year a date type, etc. This is trivial with views, but ...

For fast query performance, I usually materialize those views. But that's a multi-step and often manual process, requiring schedules or triggers. This feature combines both of those worlds, with fewer total database objects. Especially with ALTER TABLE.


No, putting data logic in a service is putting logic in the wrong layer. Data logic should live in the data layer.


It's great for speeding up queries. Optimizations frequently require giving up on absolute purity. We use computed columns a lot for speeding up specific, frequently used queries.

One example is where we need to store a special identifier code. The identifier code is required by law to have a certain format, and part of it is the date the code was generated.

Users frequently want to view items with codes generated on a specific day. For making presentation and reporting easier, as well as significantly faster, we added a computed date column that extracts the date from the code and indexed it.

Keeping an extra field in sync in code would inevitably have lead to bugs, especially as we have several different codebases which could update the id code.


I agree that it should be used sparingly. But for more than 5 years I've wished I had a computed column feature because you are often thrown into code bases that need refactoring and refactoring 100% in 1 commit is not always the safest way to go. Sometimes you want to migrate a codebase in several steps. Computed/virtual columns are an absolutely fantastic feature and it allows refactoring and migrations at your own pace, or as a fallback.


Putting business logic in the database has been a boon to large companies for decades - while the development process can be trickier, it helps ensure that every team is using the same logic (incl version) to access the data.

With PostgreSQL, you can define business logic in everyday languages, including JavaScript (plv8).


You’d hate my work, we shoehorn everything we can into the database, with a nice simple API layer of versioned stored procedures in front. It’s easy to test and enforce the business logic, and then it can be easily utilized across web, mobile apps, custom apps, and third parties.

Works great if you’re sure you won’t ever need to scale huge horizontally. One of my RoR apps connects to a schema of views, with instead of triggers calling procedures for updates. Something like 30 normalized tables are rolled up into 5 denormalized views, leaving hardly any ActiveRecord woes and still excellent performance.


Here's one of my current use cases: We have a folder tree table in our (formerly nested sets, then closure table, now materialized path), and we cache a materialized version of the path with the folder names. There are some places in the app where you can look up a node by its full path, and this column should conceptually be unique. Obviously an unindexed VARCHAR(2048) is bad to filter on, but MySQL indexes can only cover the first 767 bytes (we used utf8mb4). So we have another column, PathHash CHAR(40) AS (SHA1(path)) VIRTUAL, and then a unique index on that.




Consider applying for YC's Fall 2026 batch! Applications are open till July 27.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: