Data modeling with indexesBusiness rules
In my last post on the topic, I showed how we can define a simple computation during the indexing process. That was easy enough, for sure, but it turns out that there are quite a few use cases for this feature that go quite far from what you would expect. For example, we can use this feature as part of defining and working with business rules in our domain.
For example, let’s say that we have some logic that determine whatever a product is offered with a warranty (and for how long that warranty is valid). This is an important piece of information, obviously, but it is the kind of thing that changes on a fairly regular basis. For example, consider the following feature description:
As a user, I want to be able to see the offered warranty on the products, as well as to filter searches based on the warranty status.
Warranty rules are:
- For new products made in house, full warranty for 24 months.
- For new products from 3rd parties, parts only warranty for 6 months.
- Refurbished products by us, full warranty, for half of new warranty duration.
- Refurbished 3rd parties products, parts only warranty, 3 months.
- Used products, parts only, 1 month.
Just from reading the description, you can see that this is a business rule, which means that it is subject to many changes over time. We can obviously create a couple of fields on the document to hold the warranty information, but that means that whenever the warranty rules change, we’ll have to go through all of them again. We’ll also need to ensure that any business logic that touches the document will re-run the logic to apply the warranty computation (to be fair, these sort of things are usually done as a subscription in RavenDB, which alleviate that need).
Without further ado, here is the index to implement the logic above:
You can now query over the warranty types and it’s duration, project them from the index, etc. Whenever a document is updates, we’ll re-compute the warranty status and update the index.
This saves you from having additional fields in your model and greatly diminish the cost of queries that need to filter on warranty or its duration (since you don’t need to do this computation during the query, only once, during indexing).
If the business rule definition changes, you can update the index definition and RavenDB will effectively roll out your change to the entire dataset. That is nice, but even though I’m writing about cool RavenDB features, there are some words of cautions that I want to mention.
Putting queryable business rules in the database can greatly ease your life, but be wary of putting too much business logic in there. In general, you want your business logic to reside right next to the rest of your application code, not running in a different server in a mode that is much harder to debug, version and diagnose. And if the level of complexity involved in the business rule exceed some level (hard to define, but easy to know when you hit it), you should probably move from defining the business rules in an index to a subscription.
A RavenDB subscription allow you to get all changes to documents and apply your own logic in response. This is a reliable way to process data in RavenDB, this runs in your own code, under your own terms, so it can enjoy all the usual benefits of… well, being your code, and not mine. You can read more about them in this post and of course, the documentation.
More posts in "Data modeling with indexes" series:
- (22 Feb 2019) Event sourcing–Part III–time sensitive data
- (11 Feb 2019) Event sourcing–Part II
- (30 Jan 2019) Event sourcing–Part I
- (14 Jan 2019) Predicting the future
- (10 Jan 2019) Business rules
- (08 Jan 2019) Introduction
Comments
This example seems rather odd to me.
Typically you would sell a product with a warranty. That warranty is part of your terms and conditions / contract of sale. While you can legally improve the terms of the warranty, you cannot make them worse! Therefore, I would be very wary of creating a model where such things can easily happen.
James, I'm trying to give examples that I can both show in a blog post length that would also make sense in a business setting. I often have to make a lot of allowances for business complexity and rules in order to make the underlying point. The idea here is that you have a way to apply logic in the index and compute things which are then available for queries and projections
I wouldn't put the business logic in the index.
Instead, I would either: 1) query from the client based on the business fields I'm interested in and computed the duration there. or 2) Update the Product document with Warranty duration(s)
Why? because indexes are not tied to a specific application version. I prefer to either tie business rules to a specific application version or use a rule engine.
Use an index to compute the sales of all employees? Yes. Put a condition like 'prod.Manufacturer == "ACME' in an index? No. I thinks this should be avoided
It's just as bad as having a SQL view in the form:
If this makes you say Ewww, so should and equivalent Raven Index ...
Pop Catalin , The major difference is that it is common, and recommended, to have your index definition in your code. And RavenDB isn't typically deployed (and not recommended as such) as a shared database. It is used as an application database, rather. That means that from workflow perspective, you change the index definition by changing _your code_, not pushing schema and having to do migrations.
Granted, if you are making heavy use of this, you'll likely want to have explicit versioning of the indexes on your end, but the idea is that you have the feature available specifically so you can offload more of your work to the database.
@Ayende,
Yes, Raven indexes are extremely powerful, and sometimes, this brings too much temptation (mainly because they are in code and can easily be deployed with the app). While I favor indexes as complex as they need to be, because of the great benefits they bring, I still prefer they only contain strict aggregations of data.
Here's why:
Data modeling works best when it's done cleanly, unambiguous and using the principle of least surprise. If business logic is the responsibility of the App and access, storage and indexing is the responsibility of the database then this separation should be as strict as possible. The app need not be clever enough to compensate for the lack of database capabilities and the database should not take over the responsibilities of business logic from the App. Thankfully Raven Db does a wonderful job here, it doesn't force you in any way to put the business logic in the app for optimization purposes, and it works great for any data access patterns required by the business logic without embedding specifics.
Now while Raven DB is your product, I understand you can feel you can use it to the absolute maximum of capabilities, and sometimes even push it over the limits for demonstration purposes or simply because you have complete knowledge of its inner workings. On the other hand, in my situation as a developer, this is something I cannot afford to do on a regular basis, I must strive to use the cleanest option and keep things as separate as they can be not to create accidental messes, which later can create maintainability nightmares.
It's a simple rule which works really well in practice. (And yes it can be bent for the right reasons, but I do not like to bend it just because)
Pop Catalin, There is a lot to be said about making sure that all your business logic is the app. There have been a lot of stories about trying to push business logic to all sort of other places (in the db is the most common one) that had... unhappy endings.
However, there are cases where the definition of the business logic and where it is executed aren't necessarily the same thing, and in many cases, you want to execute the logic close to the data (in the database).
The point of this series of posts is to expose the potential that you have in utilizing this feature, the ability to run computation inside the database (which isn't new) and make it possible to query on those results (which is a feature that mostly exists only in RavenDB). Using this, you can do things that are hard / expensive / not possible using other data stores.
Comment preview