# How to design
# Micro services architecture
The project is developed under a micro-services architecture. Each component will be an independent service. We hope the micro-services we developed can be reused with other business projects easily.
Therefore, the micro-service developed is for universal purpose which should not contain any business dedicated logic.
Example of business bounded service:
// This service is bounded to specific business logic
// We cannot reuse this service for other project :(
service User {
createUser(id, name, school) <-- because of school, this service is not generic
banUser()
listUser()
}
Example of universal service:
// This service is universal
// We can reuse this service for other project, nice!
service User {
createUser(id, name)
banUser()
listUser()
}
// This service is still business bounded
// But it's OK
service UserProfile {
createProfile(id, school)
}
# Some notes on microservices:
- It is best to have the microservice work independently (a service die will not affect other service). But in real world we can't avoid dependency. You just need to keep the dependency less and clear (avoid deep/circular dependency)
- It is best to have every microservices maintain its own database. But in real world it is difficult. So it is ok to have a database shared with some closely related services. (e.g. user, user worker, cms could uses the same database)
# Design your services with scalability in mind
# Rules of thumb in scalability:
- Never store global state in your js application. Instead, store it in database, redis or other shared service.
- Make worker as a separate service. Send job to a queuing software (e.g. RabbitMQ) and consume from the queue.
- Split read-write operation. Make write to the master mysql/redis node and read from the replica.
- Avoid database transaction whenever possible. Instead, make use of messaging queue to achieve eventual consistency.
# Some technique we can use:
- Make use of cache for reading whenever possible (It is usually ok that user receive outdated data in a short period)
- Split your databases if possible. (Some unrelated table is actually never joined. Some related tables can be joined without SQL JOIN. Storing them in different database can distribute the loading from 1 single master node to multiple)
- Make use of optimistic lock for read heavy data. Use pessimstic lock for write heavy data.
- Use write-back cache strategy for read heavy data if possible (Updating a user's live location to database for every second is not needed)
- shard data to different database if possible. (Chat messages in different chat group could store in different database because we don't need to cross reference them)
- For management features like analytics/data mining, it is possible that we need to join tables from different database to create reports. Use aggregation for this instead of merge your databases into one. For analytics it is ok to have shortly outdated data.
- Splitting into different microservices so that we can scale up individual services base on their workload in production
# Make good use of third party service
# Make good use of Cache system
# Make good use of Event system
Event system is used to share event instantly. Event consumer should process it as fast as possible, the consumer should not do time consuming process on received events.
Example Publisher usage:
- Notify a config changes.
- Send real time quote data.
- Notify a user profile is updated.
Example Consumer usage:
- Received config changes, cache the config in memory.
- Received quote data, publish the quote to client via websocket.
- Received a user profile is updated, deleting the cache in redis.
Example of bad usage:
- Received a user profile is updated, send an email to the user.
For these bad usage, we should use Queue instead.
Make use of emit and boardcast
# Make good use of Queue system
Queue is to queue up time consuming jobs for compute.