While I agree with the overall idea here, something really odd struck me and that was that the author seems to assume that this would inevitably be some sort of microservice.
For so many companies, small to large, a microservice here would not just be an aditional point of failure as the article points out, but would totally over complexify the problem, when for most companies simply storing this data in their regular database, data store, whatever, and calling a relevant method to determine eligibility would suffice, and would require a hell of a lot less code.
customer.can_use_widgets? instead of customer.is_on_gold_plan?
customer.storage_allowance instead of customer.on_plan?("honkin' storage")
etc.
If you find that this won't scale at a later date then by all means turn it into a microservice, or add cacheing or whatever, but for most people that will never happen and time spent now solving the assumed scaling problems will be wasted.
Talking about an entitlement "service", not a term I see used for "another part of the same code base", talking about it being a new single point of failure, the seeming mapping out of a JSON API.
Service classes are a common pattern used in monolithic codebases to isolate related logic from the rest of the system. Everything the author described in the article can absolutely be built in a monolithic codebase sharing the same database, or separate databases if you prefer.
> Service classes are a common pattern used in monolithic codebases to isolate related logic from the rest of the system.
Yes, we have them in our code base. I'm pretty much 100% sure the author wasn't referring to these though, the way the author refers to "the service" is not how I've seen people refer to service classes.
> Everything the author described in the article can absolutely be built in a monolithic codebase sharing the same database, or separate databases if you prefer.
Yes, that, and the fact that the author seemed to assume that a microservice would be involved was what made me comment.
Any telco has been separating out call data records, billing rules and billing for years.
You should also differentiate between selling a product/plan and fulfilling it.
What you sell and what you fulfill are two different things, one is a price, the other is a cost, one is a product/PLU, the other is a SKU.
So what you're selling is a plan, which is a subscription, which has a price and other billing related stuff (periods, expiries, seats etc). The outcome of that is revenue. There may be wrinkles like discounts and coupons and free periods and etc, but that's all on your sales and billing side.
What you supply is features/services/logins etc. They have costs/expenses.
There needs to be a map between the PLU(s) and the SKU(s).
I recently worked on a payment integration which was effectively the entitlement layer as well, and I knew it was bad design at the time but hadn’t worked out a solution. It was a situation where we needed to move quickly and if it mattered later we could totally overhaul all of it, but it really rubbed me the wrong way knowing that it definitely wasn’t a good solution.
This covers everything I was unsure about, plus a lot I hadn’t considered. Thanks very much – this will save a lot of pain the next time I work on something similar.
This looks very analogous to enforcing a clear separation of authorization from authentication. So much so that I would assume that leveraging a library meant for authorization like oso[0] might be one of the most straightforward ways of implementing an entitlement system as described in the article.
Definitely useful to have queries based on feature existence with overlays (also for pricing! Discount coupons, price overrides... all very useful). All the advice about feature flags and progressive enhancement for the web, totally applicable to billing checks.
One thing I like though: try to make your feature matrix as dirt simple as possible. If you can find the pricing that just aligns with value, you don't have to play "which plans get this feature" _every single time_ cuz you know the pricing is capturing the value.
As a solo dev working on my SaaS, I can second this. My time and attention are scarce, so I ditched my earlier attempts to also have a monthly paying plan, for example. I also don't do coupons and sales. It was tempting to offer these things, but it wasn't grounded in reality. Choosing for the most dirt simple setup saves me a lot of time and it keeps the complexity at bay.
As another solo dev running a SaaS (7 years), I mostly agree. I stopped doing sales and coupons, it's a lot of effort for little practical gain (in a B2B SaaS you really care about the long-term, not hooking customers based on quick promos and coupons).
But complexity will catch up with you eventually. There is just no way around this. I read the article and I agree with most things there — my system is somewhat simpler, but I do have "features" and feature overlays, which let me override entitlements in specific cases. This gets a lot of use. And right now I'm working on plan overlays, so that I can offer custom plans when needed — and believe me, it's not because I have too much time on my hands, it's because I started to really need them.
I am a fan of "simple additional wrinkle to consider when setting up a system" style articles. For almost no cost, you get benefits that won't be apparent until later.
Good advice. But instead of having an entitlements _system_ and API calls passing through multiple different services returning lists of features a customer is entitled to, you can follow basic OOP principles and write
Somehow reminds me of a client whose new developers thought it was a good idea to fork the code base for each customer, instead of the feature flags it had..
Spent quite some time merging everything back a few years later
In general it's a good idea to separate internal billing/invoicing/plan management from your payment provider. Not always easy though as providers often have slightly different abstractions.
Same, just wrote my own billing system that supports Stripe and a few other payment methods (e.g. bank transfer), still very painful to get it right though.
It would probably take some effort to hook it up to whatever billing platform you use, but it would make it easy to turn certain features on/off based on billing. Instead of checking if a customer has a specific plan, you instead have Unleash handle the features that are on/off and then check for those flags in the code.
Thanks for the link - am I getting it right that this is a Feature Flag system akin https://www.growthbook.io/ and PostHog Flags (also OSS)? At least I got this impression from their landing page.
It looks similar, but Unleash has a lot of really awesome features that let you configure rules for when flags are enabled/disabled. Stuff like turning them on for specific domains or customer or any other metadata you can feed into it.
I'm curious why one would want a "system" or a "library"?
I run a SaaS which has a feature system. Every plan has a set of features. There are additionally feature overrides for users. Checking if a feature is available to a user is a set membership query.
With a couple more lines of code I also generate my entire pricing (plan comparison) page, so that each plan shows all available features.
Not sure where a library would provide any value, there is barely any code for this (I mean, it's Clojure, so it's very succinct and expressive, but still).
I work for developer...just found out we're launching a platform soon, for saas devs ansd start-ups to run their business - it includes payment integration. [email protected]
Revenue recognition is orthogonal to this post. The main point is that if you're selling things for money you should model those things separately from how money is used to acquire them.
Case 1 (which the article suggests we shouldn't do): Bob tries to download his contacts, we check our database and find that he's subscribed at the gold payment tier and thus is allowed to do so, and so we provide a download option.
Case 2 (proposed alternative): Bob tries to download his contacts, we check our database and find he has that privilege for the next 6 months (which we recorded previously when he subscribed at the gold tier), and so we provide that download option.
The 2nd case is probably a bit worse if you never change your payment options, but it makes changing them trivial and doesn't introduce much burden.
For so many companies, small to large, a microservice here would not just be an aditional point of failure as the article points out, but would totally over complexify the problem, when for most companies simply storing this data in their regular database, data store, whatever, and calling a relevant method to determine eligibility would suffice, and would require a hell of a lot less code.
customer.can_use_widgets? instead of customer.is_on_gold_plan?
customer.storage_allowance instead of customer.on_plan?("honkin' storage")
etc.
If you find that this won't scale at a later date then by all means turn it into a microservice, or add cacheing or whatever, but for most people that will never happen and time spent now solving the assumed scaling problems will be wasted.