Separating the business logic problem in Angular.
This article aims to give you a tool to help keep those business and UI concerns separate. It’s the result of a year’s worth of experimentation in a couple of teams I worked with. It might seem unnatural and complex at first, but then again everything new is like that right?
Optimize for the case that the software we write will change and evolve!
There are 2 problems:
- We want to separate 💸business logic from user✨interface logic! But that requires a lot of thought effort, which leads to the second problem.
- People just want to deliver their feature/fix their bug and go home! And not be constantly worrying: “Where do I put this logic?”
Why are these a problem?
1. Why does business logic need to be separate from user interface logic?
Let’s start with what each is. Think of a select list or a dropdown or a select combo box
- we’d like to keep all instances of this look and feel the same (UI)
- every instance will show different stuff (business)
In such fairly low-level concepts, it’s trivial to see how business and UI logic need to be separate. Every list will contain different entities people, addresses, orders, etc., but we’d like all of the app’s dropdown-s to look and feel the same for the user’s sake.
We slip into mixing our 💸business and ✨ UI logic when we get to more complex scenarios and get all kinds of pressure! Take your pick: I want to finish faster! I want to make it clever and future proof and reusable as f..k! My boss wants me to finish this by tonight! We promised we would deliver this sprint!
All this is true and real. What is also true and real is your future self having to maintain the code you write today. Help it and separate your business logic from the UI.
- Because business requirements evolve — add new payment method — 💸 business concern!
- Because users say — hey this wizard/form/whatever looks cool and feels natural and makes my life easy, make the other page look the same — ✨ UI concern!
- Because users say — refresh the data on this grid after adding a row — 💸 business concern!
Any of those listed above requirements become really hard or near impossible if we have all our logic in the UI component. Then look and behavior become inseparable. And such requirements are the bread and butter of what we do — evolving software to meet the user’s needs! Ideally, we would have a perfect understanding of what the users need, how to present it and when to update it! We do not! That’s why any living software has issues, bugs, enhancements.
So, let’s optimize for the case that our software will change and evolve!
2. We don’t want to think!
We, the people, need a clear set of rules as to what/how/where/when!
But G, you might say, wait a minute! I’m a smart developer, I don’t need rules! I create the rules in code!
I know, how you feel, I’ve felt the same way. The rules thing can come across insulting. Bear with me, I’ll explain.
It turns out that our brain wants to spend as little energy as possible. It’s a survival and evolutionary thing. The brain does not want to think about the same things every time and looks for the repeatable pattern to memorize and offload to the second system which automates the boring tasks. Think of tying your shoelaces, buttoning a button, washing the dishes, writing code(?!). You don’t want to constantly be thinking about these things — you’ve got more important things to worry about! Where do I go on vacation? Do I need a new car, and can I afford it? The big rocks!
OK, enough about that! Let’s say for the sake of argument that you agree with me that people need a clear set of rules to make their life bearable. Se here’s my list:
- Know your 💸business logic from your ✨UI logic!
- Keep ’em separated!
Simple! Right?
But, G, how exactly do I do that?!
One set of rules that can give you that is the Redux pattern — NgRx, MobX, etc. Its trade-off is the complex but flexible one.
Or you can implement the Page pattern.
Introducing the Page pattern
A “Page” is what the user interacts with, in one scrollable screen.
It’s not necessarily one component, though one component will often need to be the container of this “Page”! It’s not one route, it can be many, though the container component would probably have one route taking users to it.
The “Page” is the business entity:
- the user profile page
- the reservation page
- the cat owner’s page
- the billing page
- the landing page
The “Page” (1) owns the data (💸 business concern!) and it (2) requires the user input and the data render to be done on the ✨UI level:
The “Page” expects to get notified about user’s input and intent: onChangePassword onChangePasswordCancel, onChangePasswordConfirm. Also "Page" wants to know when user enters and leaves its domain: onPageEnter and onPageLeave.
The “Page” owns multi-step, 💸 business logic (BL), processes, like the delete user account with a confirmation dialog step:
How do you know what to put in it? Can the user see or click on it? Then its a ✨ UI concern — put it in the Component!
Anything else goes in the 💸 Page!
That’s a bit extreme, I know. The idea is for its implementation by us, the people, to be simple, to make the decision as to what to put where a no-brainer! Thus we take care of both the 1. Know your 💸BL and your ✨ UI! 2. Keep ’em separated!
For example, let’s say that we have a grid of bills:
- the bills are displayed on a grid with columns: status(paid/due), name (electricity home, water home, water at mum’s), due date, provider
- the bill can be drilled into for details
- the bill can be selected
- multiple bills can be selected
- bill can be paid
- bill can be hidden (some bills you don’t want to see — take care of by someone else)
The UI would be the Grid, the checkboxes on the rows representing the selection, the buttons Pay and Hide. Let’s create a component for that:
Why don’t I just put everything in the component and be done with it? This all looks too complex, too much repetition, too much! What did we gain by separating all this?
Software lives, breathes, and changes. That’s just a fact of life. Especially while developing it, but even when in maintenance mode — no new features, just bug fixes. So, let’s imagine what happens next:
Change request — Add payment method!
That would make the BillPaymentPage present a new step with a list of payment methods before showing the confirmation dialog. No need to change the UI around that. And we have the "multi-payment-method" logic all in our page where we can extract it away in case we'd like to use it in another "Page".
Change request — Use the same confirmation dialog on the logout button!
Our confirmation dialog knows only what it needs and can be easily placed in as many places as needed! No bills-payment-page-specific logic in it!
Bug — After hide the data does not refresh.
It only affects business logic so we can write a unit test, and then fix the bug, making sure we see no more of that!
How about Angular Services?
That’s a good question — why not use what the framework has provided? We can. Go right ahead. It can be used just as easy as the “Page” would. It is standard and well-known, which is a good thing for the people using it! It has a few downsides too. Very often these services start as entity specific request proxies (UserService, BillsService) and then become page-specific. What I mean is a situation similar to:
- I have a change request for the BillsPaymentPage
- I see the BillsService does the Http Requesting of the Bill entity
- I’ll just put my implementation in the BillsService
- Soon the BillsService becomes the BillsPaymentPageService, but the name remains the same
- repeat from the top for another developer for a different page
- now the BillsService is one giant ball of mud and no one can figure it out, let alone try to refactor or reuse any of its logic
And then the Page:
- it has a name that explains what it is responsible for
- it can own the data — cache it or not and it can reliably do so because it is the single source of truth
- no one will mistake the BillsOverviewPage responsibility with the BillsPaymentPage
Top comments (0)