Ask HN: Business logic is slowing us down
As developers, we're caught between keeping up with our internal stakeholders (operations, business, and data teams) and satisfying external end users. We spend a nontrivial amount of time maintaining our code to keep up with complex, ever-changing business logic.
Do we really need a code change for changing what triggers which user notifications or for updating how something gets named/logged in the database?
Unfortunately, maintaining code is also not considered valuable work. We're valued for pushing out new features, and keeping existing features up to date with changing business logic is considered short work.
To be great devs, we need to understand our users (including internal stakeholders) and our industry domain. But we also need a better way to empower business teams to control and manage the business logic, while clearing the runway for devs to build the next feature.
How has your team handled this?
41 comments
[ 5.3 ms ] story [ 97.8 ms ] threadIf your stakeholders don't understand the value of maintaining work and don't have budget set aside for that activity then that fail is on you (maybe not you personally, but your technology organization).
Empowering business teams to control and manage actual business logic, is a great idea but it doesn't come without its caveats. There's no Silver Bullet, that logic has to be stored and maintained somewhere. That logic has to have an interface to it that's callable by your system. Those interfaces need to be well-defined and everybody needs to understand exactly how the business logic affects the runtime operations. That business logic also requires version control management and release management. You also need a test environment. Guarantee some dumb ass will "fat finger" something and push it out and your system will come crashing down. If you can't roll back that change or restore the previous logic - your life is going to be hell. And do you know who's going to get the blame? You. It's your fault for letting them do something stupid.
These problems are enough to scare most people away from this insanity, but in case you're still persisten there's one more hurdle facing you: performance. You're taking logic that is currently hard-coded in with the rest of your program execution environment, and you're outsourcing it to another execution environment that has an API of some kind. Imagine what exporting the condition of an IF statement to an API is going to do for your performance! It's insanity.
I've seen many shops go down this path and I have yet to witness success. What you're advocating for is worse than the problems you have no. Sorry mate. Oh, and if you talk to IBM, they'll swear up and down that they have a tool that can do this. It's crap. I've been down that path. I've been down that path with Oracle, too. And various open source projects.
If anyone reading this comment as a different experience, a successful experience, then I'm all ears - I would love to hear it!
Another observation: If you do not have competent engineering representation in owning business units, no amount of rearchitecturing will save you. On the other hand, if you do manage to have decent engineers in each owning team while maintaning quality of your core/platform and don't wreck incentives and empowerment, things have a larger tendency to sort themselves out.
It's when you shuffle your actual insightful engineers into middle/upper management and put helpless "Senior Engineer"s in charge of entire projects with a clueless PM or burnt-out-and-checked-out ex-dev in the middle that things go south.
The solution is a maverick mind who can drive coherent code strategy.
Business logic isn't your problem, an unfaithfulness to the idea that the application is first a business logic machine may be. User interface should be a layer, instantiating and manipulating a logic API.
Configuration over code is one ideal philosophy. Design such that configuration may established what types of things, or where they might route.
This would only work if all parts of the application agree on a configuration convention.
The challenge is (as always) finding the right level of abstraction. A premature, wrong abstraction is way worse than making it work by hard coding some logic in the code, or (gasp) just copying and pasting the code a few times, until it’s clear which direction the abstraction is pointing, and at which level it’s operating at.
Like if you start building some generic workflow engine that doesn’t match your actual needs, you’re going to build something that’s clunky to work with, and that it’s hard to backtrack from the design decisions made. It’s better to hard code or duplicate something and remain noncommittal about architecture and design choices until you have clarity.
In practice it’s always a dance. You have to push the boundary to some degree or you’ll never build anything tangible. The usual advice of “just ship something” is not bad here, as shipping a complete failure will be a more information-rich data stream about the right direction than more planning.
- use a config library (or write your own) it should read the settings.config from the root home folder.
- This library should also read settings.local.config (or equivalent.) make a git IGNORE rule for the settings.local.config so you would put credentials here and they won't be checked in to the repo.
- This local config should be read and merged into the base config by your config library (deep merge).
- Further, I like to pull additional config from the DB (for user level settings.) the fs is necessary for bootstrapping into the DB, which then may load additional preferences.
- This config format should be whatever you're comfortable with, JSON , YAML, TOML, .py/js, etc. your stack will have its own selection (and behavior) for config libraries.
And now, make sure that config is loaded and made available at the earliest point in code, and everyone can use it EASILY.
You could have this stashed into a user or system cache which your tools can then spy on to see what settings are available whichever place in code.
Settings, access control, preferences, etc. can all fit this format.
By using the git excluded '.local.' layer you can ensure API keys are never checked into git.
Imagine an app where you have feature A, B, C, where A and B are basically the same feature with a few differences, and feature C is completely different. The business side of the company might decide to create three teams, one for feature A, one for features B + C, and another for features A + C. The engineers in each of these teams are immediately setup to create complexity in the long run because the correct abstraction would be A+B and then C.
I agree with team ownership. Unfortunately, teams and their ownership domains are organized in odd ways due to business needs.
However, that suggests to me that there's more reason to house the "ground truth" with the team that owns the ground truth.
If 3 teams each own different features, they'll each write their own business logic.
The solution could be something like creating two distinct domains, and then bridging the gap. You would have the engineering domain, in which abstractions follow information theory, and then you would have the business domain, which follows whatever the business wants. Then you would need some kind of bridge, whether it's teams or roles in each team, that translate and mediate back and forth. This way the business can ask for XYZ feature, the bridge teams or roles would then consult with engineers how to break these up, and then engineers would implement it. Additionally, engineers would have complete ownership over their domain.
I think the reason it doesn't work that way, is because the business side wants complete transparency over engineers, which constrains them for no reason. I also think these constrains are impacting the business value in the long run because software isn't being used to its full potential.
In any high end restaurant the menu is determined and then there’s a hierarchy of people who “implement” whatever dish.
The engineers are the line people. They’re not setting vision and they’re not doing anything beyond shipping code which is arguably like 10% of running a business, even if it’s a “tech business”.
What you described (non business-logic problems) is just "coding" level where you might integrate/compose libraries together at the boundaries of the system.
But we also need a better way to empower and serve business teams by letting them control and manage the business logic. Keep in mind, we slow down the business teams if we cannot implement fast enough.
Carrying the mindset that devs must own everything can be harmful and counterproductive. In this case, there seems to be a mutual benefit to both sides. If you don't believe so, let me know.
Business systems need to be architected around core business needs, e.g. ledgers, journals, client records, inventory. The "transactions" implementing business rules need to perform the necessary information transformations and maintain any auxiliary information as required.
A shitty PM sees problems like "when a guest at a hotel room completed {specific action X}, they want to receive a notification saying {specific message Y}".
A half decent PM will stop, step back, and ask "is there a meta-pattern here that I'm just not seeing yet", and realise the actual requirement is something like.
"When a guest at a hotel is the subject of any meaningful business event related to their booking, the management of the hotel want to be able to send them some kind of configurable, context-bound notification containing information about that event (ideally with some simple conditional logic to skip scenarios where the notification isn't relevant).
As an engineer, you're going to look at those two requirements fundamentally differently, and sure, the second one is maybe 3x harder to implement initially... But once you're 20x different "bespoke notification events" deep, you're gonna bloody wish you built the second option from day one.
Src: am Snr PM who sees teams get screwed by this type of bad PM thinking all the time.
The first time you do it, just get the job done as described. The second time, look for ways the two might be similar, but don't spend a lot of time on it. The third time, generalize and assume there will be a fourth, fifth, sixth, etc. time.
In the example the top poster gave, the first time it was requested, I would just write up some code for triggering the notification. The second time, maybe there's a shared class (or whatever) for notifications with text passed in. The third time, now maybe we need the ability to specify notification text in config or DB or through user-triggered actions.
But customers will never tell you all their requirements up front, and it's a PM's job when hearing a need from a customer to take the time to figure out if it's a single unique stand-alone problem, or simply one instance of an entire category, the other 3687 of which your customers are going to come asking for the moment they realise it's a category of problem your product is able to help with at all.
Generally I want to program for the specific case first and hopefully make it so small that it doesn’t hurt to throw it away when the requirements change. But if the specific case means the whole team has to be burdened with a lot of really obscure hospitality domain knowledge, maybe it’s quicker and easier to build the abstraction.
If a PM (or an eng for that matter) can justify the need for the complexity, either because they are extracting 2-3 use cases in to a generalizable solution, or they have 1 but know that 2-3 or more are coming real soon, then bring on the complexity.
It's a balance, for sure.
Not going to lie, this is the funniest thing I've heard all day. I've met maybe 1 PM in my entire career that actually thought that way, and that was at the beginning. The longer I'm here the more they seem focused on speed and metrics and less on the actual sysrem.
Do they get rewarded for long term thinking or short term results?
It might be worth taking a look at other self-service flow builders like the kind commonly used in marketing & analytics, where you set up a customer journey tracking page to page and write different rules depending on it, or where you set up an email drip campaign based on various configurable criteria.
The more generic and less usage-specific version of that is something like Zapier (for non-devs) or Make.com (for devs).
With that sort of abstraction, I guess your devs end up making a rules engine with a GUI and a bunch of modular nodes and integrations, like "if marketing software sends this webhook" or "send transactional email" or "increase customer value in CRM" or "notify staff to call this person", etc. that the powers that can then clobber together in whatever way they like.
The business logic understanding has been handled, but rarely implemented. Any technical system should be built off of a business system. The business should be able to define their business process maps and data points exposed in each step of the map. Most systems will still need devs to implement this unless you go with some no-code/low-code process map system.
On "How to properly manage a product roadmap": https://news.ycombinator.com/item?id=22827841
On "Should you listen to your customers": https://news.ycombinator.com/item?id=26814150
On "How to effectively get feedback from users": https://news.ycombinator.com/item?id=24971875
These mainly delve into extracting the job to be done, avoiding the XY problem, and looking at actual user behavior (actions or lack-thereof aka "non-consumption") to turn into issues/tickets to solve real problems as opposed to imaginary ones. You can even encode these in issue templates / ticket templates to avoid having frivolous feature requests or unimportant/non-urgent bugs and focus on things with high frequency and/or severity. i.e: product management.
Now, for the engineering side of it...
On "How do you develop internal tools for your organization?": https://news.ycombinator.com/item?id=29061808
The above link shows the inception of our product around a plugin architecture.
More on that: https://news.ycombinator.com/item?id=25186586
The design principles I had set and enforced with iron fingers. The fist of someone who presses on keyboard keys may be weak, but his fingers are ruthless.
https://news.ycombinator.com/item?id=29397405
One way to go about this is to improve the way you build product (product management, everyone is involved) or push for that, then get the engineering to deliver that in ways that make it easy to add/remove features, integrate with users (in the abstract sense, they could be humans or other systems), onboard developers.
Other posts you could cherry-pick from can be found here: https://news.ycombinator.com/item?id=40482961
You need to have a conversation with your colleagues about how much capacity your development team has.
Then don’t maintain it and just let it fail. Maintenance requires time and effort which costs money that someone has to pay for. If the customer/client/employer doesn’t want to pay for it then just don’t do it. Don’t be soft about this. Someone pays for this deliberately or it does not happen. If they feel emotionally conflicted they can go fuck themselves until business failure and lawsuits become the new norm.
Now, it is your responsibility, as the developer, to achieve competency. If the cost of maintenance exceeds a given percentage of total labor then one of two things must happen.
First, is resolution. Solve for the problem by proposing refactoring as a new feature. If they don’t want to pay for this then continue to let Rome burn.
Second, is ownership. Someone needs to take ownership of these expenses. I mean financial ownership on paper. That person(s) will be the one best positioned to address the concerns of maintenance, because maintenance costs money. Lowering the cost of maintenance requires research and measuring things. If nobody drives that deliberately it will not happen. If you as the developer cannot deliver the labor to achieve this then you should be replaced by someone who can.
That is all you need to know.
On the other hand I have worked for companies with a strict software development process. I’m not talking about waterfall, but agile: the systems were so over engineered that it was very difficult to write code (and it was an awful experience). Everything was done by seniors who left the company some time ago. You couldn’t really complain if you needed more time to implement something because our PMs were very clear about the requirements (so, if delays exist that’s not on them), and the code was written following good practices and standards (so again, if delays exist, is not because of the state of the code base).
I prefer to work for chaotic companies for a couple of years: more flexibility (so I can actually enjoy writing code) than for companies with obsessive practices.
Code complexity goes up with more features. But also product complexity. I was actually looking for a product that did X for my residential community. My previous job did WXYZABC. Our community went with the crappy competitor that only did X and did it at 200x the price. There were lots of features because we learned to build fast and the price was low because the costs were low. We could have charged the 200x and conquered 80% of the easy market, but we went for bigger markets that we could barely reach.
I think the ideal is having teams that do only one thing. People love to talk about how efficient WhatsApp is, creating billions of dollars of value with like 40 people or whatever it is. But nobody wants to build a WhatsApp. They all want superapps, even though these are less efficient in every way.
Back in the 1980s I was tasked with writing a system to use a handheld computer to track the manual inspection of Fire Equipment (Extinguishers, Hoses, etc) at various locations across fossil fuel based generating stations.
The very first version was hard-coded to the questions they supplied. My initial estimate was 1 month, it took 2. It met the requirements we agreed to, but it was totally unusable. Any changes to the questions required changes to the source code.
I ended up providing them with a set of forms they could use to edit questions. (This is back in the days of MS-DOS, so it was all in a TUI). To handle the ability to actually add/remove questions, I provided a set of choices for question types (Yes/No, or a number)
Then I provided 2 options, the step number to go to if the right answer was given, and the step to go to if the wrong answer was given.
It wasn't perfectly safe, they could jump to a non-existent step number, but it was easy to comprehend, so they didn't have issues fixing their own errors.
It worked well enough to set up for 10 other stations, and paid my bills for a few years.
---
Another time, the Account Managers at the Consulting Firm I worked at wanted a number of reports, all of the same general format, but with specifications that varied all over the map.
I built an SQL Query generator for them in Microsoft Access, that built (based on their selections) and showed (and allowed them to directly edit) the resulting SQL query. They were then able to cut/paste and save the queries that they liked, and build new ones on the fly.
---
If you can build a tool for them that lets them define some event, then a series of steps that should happen afterwards... they can manage the business logic directly.
Your task isn't to make it perfectly safe, but transparent enough so they can understand and build a mental model of how it acts, so they can manipulate it safely.
They need to be able to see the current rules, and perhaps even play with them in a reversible way, to test out new ideas.