Replace with huge-page mapped ring buffers, independent processes, kernel-bypass set-and-forget, buffer lap checks, file-mapped self-describing binary-formatted stats, direct-mode disk block writes, caller-provided memory.
https://github.com/userdashboard/organizations/blob/master/a... derived from https://github.com/userdashboard/organizations/blob/master/t...
Another thing that helped was moving all my UI page tests to Puppeteer which is a NodeJS API for browsing with Chrome and tentatively Firefox web browsers. This let me automatically generate screenshots for my entire UI to publish as documentation, while simultaneously testing the responsive design under different devices which surfaced many issues.
https://userdashboard.github.io/administrators/stripe-subscr... generated by https://github.com/userdashboard/userdashboard.github.io/blo...
We found it much easier to reason about logic in the code base with having many small dumb components, which didn't have any state or complex functionality. These would be controlled by a few smart parent components to coordinate them.
The result was a lot cleaner. We implemented this on a Web client, but I think the concept would work well in any codebase.... dumb classes are easier to understand
We have a very typical [web] codebase, server-side code (e.g. business rules, database access, etc), server-side Html generation, and JavaScript/CSS/Images/Fonts/etc stored elsewhere. Two repositories (content and code).
So the obvious question is: How do you manage deployment? Two repositories means two deployments, which means potential timing problems/issues/rollback difficulties.
The solution we use is painfully simple: We define the JavaScript/CSS/etc as immutable (cannot edit, cannot delete) and version it. If you want to bug fix example.js then it becomes example.js 1.0.1, 1.0.2, etc. You then need to re-point to the new version. The old versions will still exist and old/outdated references will continue to function.
This also allows our cache policy to be aggressive. We don't have to worry about browsers or intermediate proxies caching our resources for "too" long. We've never found editing files in-place, regardless of cache policy, to be reliable anyway. Some browsers seemingly ignore it (Chrome!).
We always deploy the "content" repository ahead of the "code" repository. But if we needed to rollback "code," it wouldn't matter because the old versions of "content" was never deleted or altered.
There's never a situation where we'd rollback "content" because you add, you don't edit or delete. If you added a bad version/bug, just up the version number and add the fix (or reference the older version until a fix is in "content," the old version will still be there).
Get rid of any configuration options that no one uses. These things get passed around in flags sometimes to deep levels and can make logic complicated. Don't add a configuration option until you are sitting at someone's desk and see they need it and why. Only add the bare minimum. Same for APIs, buttons, and features.
Not even being sarcastic.
Inversion of control (pass in your dependencies), keep your architecture orthogonal (make it composeable and really think if you need to inherit things rather than delegate them), code-generation of a transport api via gRPC and only focus on the business logic implementation.
Make most things immutable.
Prefer composition to extension.
Treat Types as contracts.
Sandbox "unsafe" codes (codes that interacts with network, file storage, etc).
Eliminate side effects.
Eliminate premature abstractions.
Prefer explicit over implicit.
Keep components functional.
Prioritize semantic correctness and readability.
Use events to for inter-component communication when those components don't need to care about each other's functionality.
Think protocol over data.
Here are some tips that helped me a lot:
- Keep your solution and tech-stack as simple as possible
- Mark those parts that can change often and try to make them configurable (when you have it configurable you don't need to change code and re-deploy every single adjustment)
- Make sure you have a good and readable logging
- Use DI
- Separate your application core application logic from the infrastructure part (DAL, Network Communication, Log Provider, File readers/parsers and similar)
- Keep your functions/methods clean and without side effects
- Method has to return something (try to minimize the usage of "void" methods)
- Split each feature or functionality you are working on into small pieces and compose the final thing with them
- Be disciplined about your naming conventions and code style
Methods and functions should be around 5 lines.
Doesn’t always work but is great to aim for.
You can start an entire business in consulting on test automation and you would never run out of work.
Choose Boring Technology http://boringtechnology.club/
Build your system to be level-triggered as much as possible. Its default mode should be reconciliation: examining its current state and transforming that into the desired state, especially if the current state is "something went wrong". Build in dumb reconciliation before worrying about making it more real-time.
The fewer moving parts, the better. Don't go multi-service architecture until you absolutely have to (see YAGNI, KISS).
Keep your business logic contained, separated from everything else, in ONE place. If I open up your business logic code, I shouldn't see anything about persistence, the network, etc. Similarly, I shouldn't find any business logic in your other concerns. The business logic interacts with other concerns via abstractions.
Be unforgiving when it comes to correctness guarantees. Use the type system as much as possible to make errors impossible.
- Strong test suite
- Delete duplication as much as possible by using any techniques such as method extraction and keeping classes and methods small.
So, I think, instead of layering, for example I should put everything that needs an access to a User entity's internal fields in User class itself.
For example: User.getProfileAsJson() // for sending out to frontend
Now I am confused regarding where to put methods that involves two entities. Suppose there is an Event entity which represents some online event that can be registered by the User.
Where is the best place to put getEventsRegisteredByUser()?
An example would be if you have a module that calls a REST API to get/put something (say time sheets for your invoicing app), then have that be its own module that is testable.
Create internal TimeSheet data structures that you pass to/from that module. The core functionality of your app should be implemented using the TimeSheet data structures and you can have tests that use those and then separate tests around calling an API.
New customer comes along and says they want to send you CSV files via SFTP (yuck, but they got money). You just have to write a new interface that works with exchanging those files and gets them into your TimeSheet data structures, the core of your app should remain unchanged.
A few examples here.[2]
1. https://drewdevault.com/2019/02/25/Using-git-with-discipline...
2. https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/lin...