Nine Lessons from Building a White-Label Reverse Logistics Platform
I spent time leading work on a large white-label reverse logistics platform. Each client had their own rules, flows, customizations, and styles. After that project, I wrote down what stuck with me. These are the lessons I keep coming back to.
Be prepared for a lot of changes
This is intrinsic to the product. Because it was white-label, every new feature or adjustment had to preserve that extreme customizability. The code that enables customization also needs to be good enough to allow more extensions. If a new client asks for a new piece of the flow or the UI, the code should already be prepared to support it.
Consider the extreme scenarios
Whenever you build a feature, think about the massive-scale scenario. API requests and webhook calls can reach thousands in a few minutes. Hundreds of clients can access the same page and list the same data. The same flow can be triggered thousands of times along the day, and it should be performant enough to not create bottlenecks.
To handle this properly, you need more than good code. You also need a good data model and proper database strategies, like creating good indexes or preferring a secondary replica to read data instead of the primary one. These extreme scenarios are also helpful to decide the importance and the order of actions.
Differentiate immediate behaviors from enqueued ones
Know how to define and differentiate behaviors that should be triggered immediately from behaviors that could be enqueued or run in a background job.
Email sendings and other types of notifications can be enqueued. They don't need to be a sync, blocking operation in the flow. Other operations, like updating a secondary collection or recalculating a specific number, can also be moved to an async job instead of blocking the main flow.
But other actions should be marked as a crucial part of the flow. For example, you need to await a status change. If a status doesn't change correctly, the flow should be aborted because it will create issues downstream.
It's also important to understand what the sync and blocking operations do versus what the async and enqueued operations do. If they are not properly implemented, you can get race condition issues. If a sync operation depends on an async operation, or vice versa, maybe you need to readjust your code.
And if a flow has many crucial parts, it can be slow. The user should have good feedback on the UI so they know why it's taking longer than expected.
Consider the reversibility of actions
Sometimes a status can be changed to a previous one, or an action can be undone. Your code and your platform should be prepared to undo actions and make atomic, reversible actions without side effects. This prevents inconsistency in the database and avoids allocating your team's time to manual fixes.
Use logs as audit traces
Each log should have enough context so the dev who will read it (or the agent that will read it) has enough data to understand what happened at that time. With enough context, you can also filter logs properly, e.g. just the logs for a specific user ID.
A good log should be good enough for both a human and an AI to understand the complete timeline of events.
Each logic should have a single source of truth
That's wider knowledge spread in the developer world, but it's important to mention it here. A large system like this one has a lot of logic and decisions being made in every part of the code. Each crucial part should have its own single source of truth: a dedicated file or function where that decision lives, and it should be properly tested.
Also, if the same flow is spread across many parts, that flow is also considered a specific decision or logic and should be centralized and tested.
The ideal is that each decision should not have side effects. A single piece of the core product should not be responsible for breaking the other parts. They should be isolated. As mentioned above, we should always consider the option to reverse actions. With that in mind, we can create code that isolates each decision separately and ensures that different pieces won't affect each other.
The customer is not always right
Sometimes customers send feedback or ask for new features, fixes, or tweaks in the flow that make sense at first glance. But if you think it through, the same problem can be solved in a different way than what was requested, or the request can be discarded entirely if it strays from the product goal.
A customer can ask for a feature that works in way X. However, if you dig deeper, you may understand that based on the feature request, the customer's pain can be solved even better by implementing a different feature in way Y. The client is not always 100% correct in the request. It can be discarded, or the solution can be completely different and still solve the issue.
Work closer to the product owner and product manager
As a leader, you need to understand the product better. You need to understand the flows, why each feature was implemented, and which pains each feature solves. If you understand the product goal and what customers expect from your product, you can make better decisions.
With a full overview of the system and the product, you can analyze a client's pain more accurately and discuss customer requests with stronger arguments alongside your team and POs. You can choose the path that benefits everyone: the devs, the board, and the customer. It's not always an easy decision, but it's better to take a hard right decision than an easy wrong one.
Leadership: don't be the bottleneck
You as a leader should not be the bottleneck of the team. Using AI, devs could open many PRs in a day, and you should be smart enough to also use AI to speed up your reviews.
Implementation should be fast so you can spend more time on the details and discussion about a task with your team, to avoid blocking the dev in the middle of the sprint.
Also, you don't need to be a perfectionist. Perfect code doesn't exist. You should take care of the project patterns and good practices and make sure that the code is being tested correctly. This can be automated with AI and with the proper workflow tools.
Conclusion
I hope any of these lessons can be helpful to you in your dev career.
See you next time!