There are tons of articles online that offer tutorials about how to use Promise.all. I will not reiterate those. In this article, I aim to brief summarise the use of Promise.all in real-world projects for those programmers transitioning from junior to mid-level.
What do we need to keep in mind when considering using Promise.all
Order matters. Promise.all returns the results in the same order as the source.
Fail one, Fail all. If one of the promise rejects, then the Promise.all will not wait for the remaining promise to execute but rejects right away.
When do we need Promise.all
In real project, normally when we have an array of data, for each element in the array, we need to do some async/await operations, this is where we need Promise.all. For example, in the following code snippets from a real project, we have an array called idsForDelete, for each id in this array, we need to call an async method removeMonitorTask(apiKey, id). Therefore, we use Promise.all to do async/await calls on all ids in the array.
Overengineering is a word that a software engineer will hear about from time to time through his/her whole career. I recalled a design case that I encountered a few months back that perfectly explain what is overengineering in practice.
The Definition of Overengineering
On Wikipedia, Overengineering is defined as “the act of designing a product or providing a solution to a problem in an elaborate or complicated manner, where a simpler solution can be demonstrated to exist with the same efficiency and effectiveness as that of the original design.” I would like to simplify it as: choose a more complicated solution to resolve a problem when there exist simpler ones.
How to avoid overengineering
The tricky thing is, in practice, normally it is hard to identify overengineering in the system that is designed by yourself.
From my own personal experience, except harness yourself with rich knowledge and hands on experiences, the best option is to leverage collective wisdom, i.e. consult as many people as you can.
Case Study
A few months ago, my team had a design decision to make for a web system that is under developing. The scenario can be simplified as:
A group of users, on peak time, the QPS was estimated as around 5K, would request and compete for a limited number of resources (the number of the resources would be less than 500) in a pool.
Users would request the resources via RestAPI call. The users’ requests would be served in First Come First Serve style, non pre-emptive.
The individual resource itself has a rate limiting, if it receives more than 20 requests per minute, then it will return an error and become unavailable for a few minutes.
VIP users could be assigned a resource exclusively and still under the “20 requests per minute” rule.
According to internal tests (from the ones who were not from the dev team), the possibility that requests from the user might take more than 3 seconds to finish was high (roughly more then 98%).
The design is targeted for resolving two issues of the scenarios:
Resources competition.
How should the system handle the case that multiple users competing for the limited number of resources?
A single resource received more then 20 requests per minute.
How should the system handle the situation that a resource receives more then 20 requests per minute from a VIP user?
One of my colleague instantly proposed using message queues as the core of the solution. To be specific:
for “Resources Competition”
All the user requests should be put into a message queue and be pulled out in FIFO style.
A worker would pick a request and assign a resource once there is any available ones in the pool.
for “More than 20 Requests issue”
Assign a message queue for each resource, store the requests sent from the user into a message queue, a worker pull requests from the queue in FIFO style.
Only let the requests be pulled out from the queue under the rate of 20 per minute.
The solution is shown below.
Fig.1 - MQ Solution
Add graph
Everything seems perfectly “reasonable” right now. However, in the evening of the same day, I gave the problem a second thought, then I realised that message queues might be an overengineering solution.
Resources Competition
From User Experiences Perspective
Resources Competition. On the one hand, let us assume if a message queue is not used here, then once all the resources are assigned, a user will receive an error for the resource request and the user will be informed by a message that he/she should retry after a few minutes. On the other hand, if a message queue is adopted here and all the resources are assigned, then a user’s request will store in the queue and the user will need to wait for the response for unforeseeable time, which does not improve user experiences compare to the first case.
From System Performance Perspective
Well, it is obvious that introducing message queue will not improve system performance.
Therefore, for “Resources Competition”, adopting a message queue is an overengineering option.
More than 20 Requests issue
I was thinking about forcing a user to wait for 3 seconds as an alternative option, i.e. each time the user consumes the resource and takes less than 3 seconds (60 seconds / 20 = 3), then force the user to wait until 3 seconds passed (3 seconds count from taking the ownership of the resource to release it), it ensures that the user will not trigger the 20 requests per minutes error.
From User Experiences Perspective
Consider the fact that more then 98% of chance that a user’s request will take more than 3 seconds to complete, it means that forcing a user to wait for 3 seconds passed if a request takes less than 3 seconds will not significantly impact user experiences.
From System Performance Perspective
One resource per message queue add heavy burden on the backend system while it does not improve the system performance in any means compare to the forcing wait method.
Therefore, for “More than 20 Requests issue”, using message queue solution is an overengineering option too.
With the arguments and conclusions in mind, the other day, I successfully convince the team to avoid adopting message queue which relief the backend dev team :).
Recently, I helped some small business (less then 20 people) integrating payment module into their systems. I consider that it worths sharing the experiences, as this also benefit the cases include: selling code on Github, selling online courses on personal websites etc.
The payment platform manages everything for you
There are some payment platforms that manage everything for you. You do not need to bother to design the frontend UIs of “placing order” page (choosing the products listed on the page) and “paying” page (filling credit card information and actually transferring the money), not to mention the backend logics behind the UIs. The platforms do all these things for you. Let us take lemon squeezy as an example(Well, I swear I did not receive ads fee from lemon squeezy, but maybe they should do so :D).
For integrating lemon squeezy into a system, all a software engineer needs to do is:
Set a store page on lemon squeezy, fill the page with information includes: product information and payment information, as shown below. The customers should complete all the steps regarding buying products on the store page: select products, filling payment information and transferring money.
Fig.1 - An example of lemon squeezy store page
Add a link to the frontend page where you would like to redirect the customers to the store page of lemon squeezy
Add a page for customers to activate the keys recieved from lemon squeezy. After a customer complete payment on the store page, lemon squeezy will return an activation key to the customer, the customer should use the key to claim the ownership of the product on the website. On the backend of the website, when activating the key, the software engineer needs to do two things: 1) validate the key with lemon squeezy; 2) activate the key with lemon squeezy.
With such payment platform, the software engineer only need to save the activation key and the order id returned from lemon squeezy. lemon squeezy provide a orders page for the store owners to check order information, as shown below.
Fig.1 - The orders page provided by **lemon squeezy**
Cons:
There is no perfect solution in reality. The cons of using lemon squeezy as I observed from my own experiences are:
User experience. Rediect to a totally seperate payment page affect user experiences regarding online purchasing in a bad way. In some countries, the page loading speed may need up to 30 seconds.
Trust issue. As the domain name for the lemon squeezy store page is different from the source site, it may create trust issue for the potential customers
Less control. High managed platform means little spaces for customization. For instance, the layout of the store page UIs are not in the control.
Type 2: Manage everything on your own
For this type of platform, a typical work flow is:
1
select products --> select payment method --> place the order to the platform via RestAPI --> the platform returns a qrcode link for the customer to pay --> nortify the website the payment has completed
in which you have everything in control, i.e. design UIs of products page and payment page, handling all the backend logics.
Pros and Cons
The cons of adopting the type of platform is that it may takes time to build it as everything is under your control. And the pros are clearly the opposite side of the cons of type 1 platform.
The system needs to accept subcriptions from users with certain period time. After the paid subscription expires, the subscription status in user profile should be downgraded from a subscriped user to a normal user.
Make A Choice among Two Options
MongoDB TTL Index vs Node Schedule
As introduced in one of my previous writing regarding the System Architect , the system uses MongoDB to store user data. To add the function into the system, there were two choices that immediately came to my mind: MongoDB TTL Indexes and Node Schedule
Node Schedule Node schedule runs on the server side, for example, node-schedule. But since the requirement is to change user’s subscription status after certain amount of time (relatively long, like a week, a month etc.), and it will only run once, putting such burden on the server side for such task is not worth it.
MongoDB TTL Indexes Compare to Node Schedule, MongoDB TTL Indexes run on the database side, it will not create extra jobs on the server, it is a better choice for such requirement.
MongoDB TTL Indexes
Directly create MongoDB TTL Indexes
Clearly, in my case, I need to create MongoDB TTL Indexes with give parameters after the schema is generated. And after searching for a while, I realised that in mongoose, I can only create TTL indexes when create the schema, it is not possible to create TTL indexes with give parameters when the app is running (please correct me if I am wrong here). Within mongoose, it only can introduce TTL Index within schema definition, as shown below:
constXXXSchema = newSchema( { username: { type: String, required: true, unique: true, }, }, // timestamp of the creation data of the user { timestamps: true } );
Luckily, there is an alternative method to save my life: using expireAt in the schema. Under the table, the expireAt generates TTL indexes in MongoDB. Below are how I did it:
introduce expireAt in the schema with expires property
Recently, I have been busy working on a project start from sctrach – from business idea to a software product running online. I literally complete the design and most of coding job myself (well, I also deeply involded in devops as well). The project is about to go online now. I would like to spend a little bit of time to discuss about the system design of the project.
Business Idea
As it is a chit-chat about system design in real case, I will briefly introduce the business without mentioning too many details.
The system provides a kind of service for registered users that is based on purchased usage quota, i.e., everytime a user consume the service, the number of service usage limit belongs to the user will be reduced by ONE, until it reaches to ZERO. Then the user needs to further purchase the service.
In essence, the system needs:
a user management service
a mechanism that is able to change the user data in ‘real time’
System Design
System Design for the Business
User Management Service
A typical user management module, I chose mongodb for the following 3 reasons:
the user schema design was not fixed at the beginning, to avoid data schema changing troubles with relational DB, I went with NoSQL
The better sharding and scalability provied by mongodb as it is json document based.
To change the user data in ‘real-time’, I consider caching user data in memory for fast I/O, and I only want to cache partial user data, not all of them, therefore, NoSQL is a better option.
Change User Data in ‘Real-time’
As I mentioned, everytime a user consumes the service, the service usage quota will minus ONE. Since the service could be used by multiple users at the same time, the system must not spend too much time on I/O regading changing the user data, hence, I consider use in-memory cache here in stead of updating data directly in mongodb. That is the place where Redis will play.
As shown in the figure, when a user login, the user data will be loaded into Redis, and everytime the user consumes the service, the backend side will update the data in he Redis, and only write back to mongoDB when the user logout.
Sync user data between frontend and backend
At the frontend side, the user may need to see the number of service usage in ‘real-time’. There are two choices:
Frontend always keey the data sync with backend, means that everytime the service is used, the frontend will invoke REST API of backend and wait for returned result.
Frontend and backend use different data set. To be more specific, the frontend caches the user data in react-redux (only work with data from react-redux), and everytime the user consumes the service, on the frontend side, it change the number in react-redux, at the same time, invoke REST API to update the number in redis on the backend side.
For the 1st choice, the frontend side will always show the correct data but it sacrifices time. For the 2nd one, the frontend may show different data from backend (if something wrong happens on the backend side regarding updating data in redis), but the frontend side does not need to wait for the REST API call result.
I went for the 2nd choice for speed.
Locking the Serivce
The service will be used by multiple users at the same time and it is not sharable. Therefore, I need a distributed lock here.
Since I have introduced redis for caching, I used redis redlock for distributed locking.
These are the design decisions I have made during the project. System design is always about trade-offs: space, time, cost. The most import one: do not over design, the priority is to meet the business requirements not to create a technically perfect product.
Recently, I need to do load tests for a set of REST APIs under Node.js environment. After studying some online articles, I decided to give it a go with AutoCannon. My scenario is that I have already had a sets of requests setup in Postman, and I do not want to rewrite everything for the load test. Luckily, I found a solution that exactly match my requirement (listed below), but I still want to write my own version from a AutoCannon fresher’s perspective and hopefully will be useful for future readers.
Step.1 Export A Collection of Requests from Postman
As shown below, left click the “…” button at the right side of the requests collection. Then choose “Export” in the popup menu.
Fig.1 - Export Requests Collection
Afer this step, we should receive a JON file contains all the information of the REST APIs we would like to test.
Step.2 Write Code for Load Testing
We need to create a sepearte xxx.js file that tells AutoCannon what to do.
Then we should able to see output like this for each invidual request:
Fig.2 - API1 resultFig.3 - API2 result
For sure, there are more details left to discover, e.g. settings of _autocannon_, but that is left for reading and searching the official document :)
Winston logger is commonly used logger in Node.js. Most of the case, I use winston logger and also integration outputs of morgan into winston. I recalled that the first time I used winston, I followed some of the online tutorials regarding how to config and use it, but there are always problems here and there for my scenarios. Therefore, I decide to write this to blog to introduce how I config winston logger in my projects.
Project Structure
I normally put the configuration file of winston logger like the following:
For the following settings, please refer to the official site of winston logger. I would emphasize two things:
On the transports part. In the following configuration, I put loggers into files under the directory /log/*.log files. That is a very basic settings, you can of course adding settings like, generate new log files after the current log file reach to certain size, and further config organizing log files according to their generated date.
If not in “production” environment, I set the logger still output on the console for facilitating debugging.
const morgan = require('morgan'); const logger = require('../config/winston');
const morganMiddleware = morgan( // Define message format string (this is the default one). // The message format is made from tokens, and each token is // defined inside the Morgan library. // You can create your custom token to show what do you want from a request. 'combined', // Options: in this case, I overwrote the stream and the skip logic. // See the methods above. { stream: logger.stream }, );
While talking about TDD within developing web systems, we cannot avoid testing RESTful APIs. I have seen some turtorials online (e.g. the articles which discuss about how to do unit test for REST API via leveraging certain npm packages under Nodejs environment) which mention that for Unit Test (hereafter, UT) it will hit database.
Well, in my humble option, in UT we should not hit database. The core idea of unit test is “Isolation“, i.e. isolate the function/methods under testing, presume everthing that the function/methods will interact with works as expected, and see if the function/methods generate the result we are hoping for. Therefore, if hitting database in UT, the testing result of the UT will depend on the status of the database, the coupling between the function/methods and the database makes the test is not a real “Unit Test”. Tests that requires access database should categories as Integration Test (hereafter IT) or End-to-End test. In the rest of this article, I will explain in details regarding how to do UT and IT for RESTful APIs with sourcecode samples provided under Nodejs environment.
Integration Test for RESTful API under Nodejs Environment: using MongoDB and supertest as an example
Assume that we want to test the following REST API: “POST /api/auth/signup“, we have the following code in our project:
To test the api in integration test, we will need to complete two steps:
connect to database for testing purpose only, not database in production environment
test whether the api output expected results
1. Set a Mongodb connection selector between production environment and testing environment
app.js
1 2 3 4 5 6 7 8 9 10 11 12
// According to the value of process.env, choose the connection between prod env and test env const mongodbConnect = process.env.NODE_ENV === "test"? process.env.MONGODB_CLOUD_TEST : process.env.MONGODB_CLOUD_PROD;
constdbConnect = async () => { try { await mongoose.connect(mongodbConnect); winstonLogger.info(`Connect to mongodb of ${process.env.NODE_ENV}`); } catch (err) { winstonLogger.error(`Failed to connect to mongodb due to ${err}`); } };
it('post a new user and respond with 200 and a msg showes that user has been created', async () => { // a real call to the database in testing environment const { text, status } = awaitrequest(app) .post('/api/auth/signup') .set('Accept', 'application/json') .send(tmpUser);
expect(status).to.be.equal(200); expect(text).to.be.equal('User has been created!') }); ... });
As it can be seen from the above code snippet, the supertest makes a call to the database for creating a user profile, and the user profile is actually created in the database under test environment. Since the test involving testing the connection with a real database, I would like to categories this type of tests as integration test.
Then, how the unit test for the REST API should look like?
Unit Test for RESTful API under Nodejs Environment: using MongoDB and supertest + nock as an example
To clearly tell the difference between Integration Test and Unit Test, in this part I will still use supertest as the agent (alternative option can be axios, for example) to file a request to the “POST /api/auth/signup“, but in Unit Test, I will not let the request hitting datacase via http server, instead, I use nock to intercept the request and return the expected result, as shown blow:
describe('POST signup', () => {
const tmpUser = {
name: 'testaa',
email: 'testaa@gmail.com',
password: '1234'
};
it('post a new user and respond with 200 and a msg showes that user has been created', async () => {
// file http request to the REST API
const signupUser = async () => {
return await request('http://test.com')
.post('/api/auth/signup')
.set('Accept', 'application/json')
.send(tmpUser);
};
// mock http server: check if the server received expected params in request.body, then reply expected response
nock('http://test.com')
.post('/api/auth/signup', body => {
expect(body.name).to.be.equal('testaa');
expect(body.email).to.be.equal('testaa@gmail.com');
expect(body.password).to.be.equal('1234');
return true;
})
.reply(200, 'User has been created!');
const { text, status } = await signupUser();
expect(status).to.be.equal(200);
expect(text).to.be.equal('User has been created!')
});
...
});
In the above unit test, I use nock to mock a http server. The supertest still file a request, but the request will be intercepted by the mock server and return expected results, instead of reaching a real server and further hitting a real database. This is the way how isolation is achieved: I am not coupling the test with a real database, based on the assumption that all other parts works okay (via mocking), everything is completed within the test method.
A further questions may be asked is that what is point to do a Unit Test like this? Should I include the unit tests in my project? The answer is Yes and No.
No: if you are developing a relatively small system as software vendor for a small business, or you are structuring a new software product that are targeting a small number of users, not aiming for millions or even billions users in the future, then I would say do not bother to add such unit test in your system. The ROI is not worth it, keep the integration test that check the connection with real database is enough.
Yes: if you are developing a system either big or small in a big company, or if you have a software product that is aiming for millions of daily users in the future (which means the product will be owned by a big company), I would say you probably will need to add the unit tests in your system. The reason? I would quote a saying from a movie called <Kingdom of Heaven>: nothing, everything.
I spent a few weeks to develop and set up my own website. The registered domain is root domain (i.e. example.com) and the DNS setting on GoDaddy is to redirect the root domain(example.com) to the www subdomain (e.g. www.example.com). The last step before publishing is to add SSL certificates to my root domain and www subdomain. I found a nice tutorial online regarding how to do it Let’s Encrypt on Heroku with DNS Domain Validation. However, it turned out that follow the intructions I could not generate certificate and private key for my www subdomain. I will show the issue and explain how I resolve it in details.
Lovely "Lock":)
The Issue
My ultimate goal is to let https://www.example.com work on browsers. I have three custom domain in my Heroku app: *.exmaple.com, exmaple.com, www.example.com.
Please enter the domain name(s) you would like on your certificate (comma and/or space separated) (Enter 'c' to cancel):
later at the stage of applying the certificate to Heroku “sudo heroku certs:add –type=…/certificate …/privatekey”, the heroku will not provide domain name www.example.com as an option for the certificate but only example.com. This leads to the result of www.example.com is not secured.
Resolution
The resolution is easy but took me a lot of time to find it. The trick is using wildcard subdomain. At the stage of providing domain name to Let’s Encrypt, I used *.exmample.com, then later when I apply the generated certificate and private key to Heroku, it offers www.exmample.com as an option.
1 2
=== Almost done! Which of these domains on this application would you like this certificate associated with? ? Select domains www.jingjies-blog.com
Then check on Heroku, I can see the following:
The wildcard certificateSecured www.jingjies-blog.com
I planned to add a site visit counter and page view counter into my blog (Hexo + Express.js). And by searching online, it seems that there is very limited information can be found on how to add such things on a Hexo Blog where:
The Hexo blog is integrated into an existing Express server, i.e. the blog should be visited at express_site/blog
The theme I used for the blog does not have such feature added, means I cannot just add something to the _config.yml of the theme and the view counter will work as a charm. I will need to customize it.
There was several options that I had in mind:
Only customize Hexo blog and the theme –> I realize it is not feasible. I do not know how to let Hexo or the theme load customised .js file under Hexo + Express structure;
Load js from Express side –> I finally use this method. Let Express loading related js file as Express static files, the js file will add the data to the view counter in Hexo pages. Blow is what I got in the end.View counter in blog post
Now I will introduce how I achive it in details.
Set up view counter
Set up Firebase (There are tons of tutorial out there, so I am not going to repeat it:))
There is only one thing I would like to emphasize is the database set up. The database should be Realtime Database, and the “rules” is set as below. I will explain in details later why this will not cause security issues.
Firebase Realtime Databse Rules
After register and setting up the Firebase, add the following code to the hexo_blog_directory/themes/{your theme}/layout/xxx.ejs where you want to show the view counter.
After the above steps, the view counter is ready to go :)
Extra issue: is it okay to expose Firebase apiKey?
When you see that I put apiKey etc. in a static .js file, you must have a question in mind: is that a security issue here? I had the same question. Then I found the following article: Is it safe to expose Firebase apiKey to the public?
The answer is a bit long, so I will sum up here: for a small size and not a critical site, it is okay to do thing like this. The apiKey here in Firebase is not like the traditional api keys we know, it is okay to expose it. All you need to do is to set up whitelist site for Firebase (that is why I mentioend earlier in the article that you could set the Firebase database rules for opening read/write public). Here is how you can do it in details:
Go to Firebase Console
Check Authentication Menu -> Sign-in method tab
Scroll down to Authorized domains
Click “Add Domain” button and add your domains (localhost is added as default, and I do not delete as it is needed for local testing)
Set up whitelist site
The above should be okay for a small site. I am not going to over-engineering it, but you can always tailor the security rules accoriding to your business requirements :)
You are more then welcome to cite the content of this blog, but please do indicate the original reference link.