Skip to content

Commit

Permalink
docs: add docs to help readablility (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
domingo1021 authored Jun 30, 2024
1 parent 9bb93f3 commit 4e9430f
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 25 deletions.
107 changes: 98 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
# Hero Project API

## Description
> A backend API project for a game, providing two unified API interfaces to display hero information and profiles to users.
A Hero project backend server.
## Problem Statement

## Tech Stacks
**Hero** gaming backend is designed to provide hero profiles to users via an [External API](https://github.com/hahow/hahow-recruit/blob/master/backend.md#%E6%88%91%E5%80%91%E6%89%80%E6%8F%90%E4%BE%9B%E7%9A%84%E8%B3%87%E6%96%99%E5%8F%8A-api). However, this external API is somewhat unstable, occasionally returning unexpected response body formats or status codes. To provide a reliable service, the Hero Project needs to devise a strategy to overcome this issue.

1. `TypeScript` as **programming language**, which is a superset of JS, and also using **Node.js** as runtime.
2. `NestJS` as **backend framework**, and using express as internal library implementation.
3. `ESList` as **JavaScript Linter**
4. `Prettier` as **JavaScript Formatter**
5. `Husky` as **git hooks** to make sure code quality with **pre-commit** and **pre-push** scripts.
## Hypothesis

1. After a deep dive into the business logic, we found that the **frequency of read operations is significantly higher than that of write operations**.
2. The external API's data pertains to **Heroes**, the characters in the game. This data changes infrequently, approximately once a week or at most once per day. We assume that updates occur during off-peak times (e.g., 4:00 AM).
3. Temporary data inconsistencies within a short period are acceptable for the gaming system.

## Solution

1. Implement a caching mechanism in the gaming backend API: When a user calls the API to retrieve **Hero** data, the request lifecycle might be: `presentation layer` --> `business layer` --> `data access layer`. Knowing that the bottleneck lies in the unstable data layer, we can introduce a caching mechanism before accessing the infrequently changing data source.
2. Design a cache strategy for the use case: We employ the **Cache-Aside** caching strategy, which writes data into the cache after the first **cache miss**, making the same **data available in memory for subsequent requests**.

## Running the app

### Start with Docker compose
### Start with Docker Compose

Docker compose will run multiple containers and start app after dependencies get ready.

Expand Down Expand Up @@ -62,3 +67,87 @@ $ npm run start:prod
# unit tests
$ npm run test
```

## Project structure

```
.
├── config Local config for vault
├── logs Logs folder for winston (Node.js logger)
├── scripts Utility scripts (e.g., K6 load test)
├── src Source code
│ ├── cores Utility components for modules
│ │ ├── constants Constant variables
│ │ ├── decorators Custom decorators
│ │ ├── exceptions Exception filter
│ │ ├── guards Authentication guard
│ │ ├── interceptors NestJS lifecycle interceptor
│ │ ├── middlewares NestJS Middleware
│ │ └── types Type definitions for the project
│ └── modules Modules for specific functionalities
│ ├── app Root module
│ ├── auth Authentication module
│ ├── cache Cache module (access to Redis)
│ ├── hero Hero module (endpoint, biz logics, data fetching)
│ │ └── dto Hero data transfer object(DTO)
│ ├── http HTTP client module (access to 3rd party API)
│ └── logger Logger module
│ └── type Interface for system/application logger
└── test Tests (organized by modules)
└── modules
├── app
├── hero
└── http
```

## Tech Stacks

### General

1. `TypeScript` as **programming language**, which is a superset of JS, and also using **Node.js** as runtime.
2. `NestJS` as **backend framework**, and using express as internal library implementation.
3. `Redis` as **Cache**, helps us store data from external API
4. `Vault` as **Secret Manager**, stores Environment variable or Secrets
5. `Docker` as **Container Platform**, helps to containerize application.
6. `Github Actions` as **Continuous Integration** tool, runs test every time PR is created or when code is merged into **main** branch.
7. `ESList` as **JavaScript Linter**
8. `Prettier` as **JavaScript Formatter**
9. `Husky` as **git hooks** to make sure code quality with **pre-commit** and **pre-push** scripts.

### Library

1. Production pacakge:
- HTTP client
- `@nestjs/axios`: `axios` HTTP client compatible with `NestJS` framework.
- Cache
- `@nestjs/cache-manager`: Cache Manager which help NestJS wrap cache client
- `cache-manager`: Cache module for Node.js, support various storage type, including in-memory and Redis
- `cache-manager-redis-store`: cache-manager plugin to use Redis as cache store.
- `redis`: Regis client in Node.js
- Logger
- `winston`: Node.js Logging library which supports log levels, log storage
- `winston-daily-rotate-file`: Help winston to transport & rotate log into files.
- Data validation
- `class-validator`: Validate input parameters at Controller
- `class-transformer`: Transform data from http request to controller input parameter
- Configuration / Env
- `config`: adding secrets / env variable from config file (set in `NODE_CONFIG_DIR`)
- Utils
- `rxjs`: Reactive programming package using Observables
- `uuid`: UUID generator
- `bcrypt`: Help hash / compare secrets (e.g user password)
2. Development package
- Test
- `jest`: Unit test framework, help spy & mock components.
- `supertest`: Library for testing HTTP servers, used to check API endpoints.
- `nock`: HTTP interceptor, help to loose the dependency to external API when test.
- Code Quality
- `eslint`: TS & JS Linter, help to check coding style / standard.
- `prettier`: TS & JS formatter, automatically format code with standard in **.prettierrc**
- `husky`: Git hook, help to check coding style at git related hook point(e.g. **pre-commit** / **pre-push**)
- `lint-staged`: Linter for git staged files.

## Comment Priciple

1. **Class Domumentation** & **Function Documentation**: Describe the purpose of the class, methods, or function, its parameters, and its return value using JSDoc.
2. **Special Case Comments**: Use inline comments to explain complex logic, special cases, and important decisions within the code.
3 changes: 1 addition & 2 deletions src/cores/guards/local.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { AuthService } from '#auth/auth.service';
import { InternalRequest } from '#cores/types';

/**
* @description Guard to authenticate the user with local strategy.
* @implements Set isAuthenicated to the request.user object.
* @description Guard to authenticate the user with local strategy, set authentication result to the `request.user` object.
*/
@Injectable()
export class LocalAuthGuard implements CanActivate {
Expand Down
11 changes: 4 additions & 7 deletions src/modules/hero/hero.repository.impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class HeroRepositoryImpl implements HeroRepository {
) {}

/**
* @description Get all heroes with read-through strategy. Get cache first, if not exist, fetch from external API.
* @description Get all heroes in cache-aside strategy: get cache first, if not exist, fetch from external API.
* @returns {Promise<Array<Hero>>}
* @throws {InternalServerErrorException}
*/
Expand All @@ -34,8 +34,7 @@ export class HeroRepositoryImpl implements HeroRepository {
}

/**
* @description Get all heroes in read-through strategy.
* @implements Get cache first, if not exist, fetch from external API.
* @description Get all heroes in cache-aside strategy: get cache first, if not exist, fetch from external API.
* @returns {Promise<Array<Hero>>}
* @throws {InternalServerErrorException}
*/
Expand All @@ -58,8 +57,7 @@ export class HeroRepositoryImpl implements HeroRepository {
}

/**
* @description Get single hero in read-through strategy.
* @implements Get cache first, if not exist, fetch from external API.
* @description Get single hero in cache-aside strategy: get cache first, if not exist, fetch from external API.
* @returns {Promise<Array<Hero>>}
* @throws {InternalServerErrorException}
*/
Expand All @@ -77,8 +75,7 @@ export class HeroRepositoryImpl implements HeroRepository {
}

/**
* @description Get single hero with profile in read-through strategy.
* @implements Get cache first, if not exist, fetch from external API.
* @description Get single hero with profile in cache-aside strategy: get cache first, if not exist, fetch from external API.
* @returns {Promise<Array<Hero>>}
* @throws {InternalServerErrorException}
*/
Expand Down
10 changes: 3 additions & 7 deletions src/modules/hero/hero.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@ import { Hero, HeroRepository } from '#hero/dto';

@Injectable()
export class HeroService {
constructor(
@Inject('HeroRepository') private readonly heroRepo: HeroRepository,
) {}
constructor(@Inject('HeroRepository') private readonly heroRepo: HeroRepository) {}

/**
* @description Get all heroes
* @implements Get all heroes with profile if authenticated, otherwise get all heroes only
* @description Get all heroes with profile if authenticated, otherwise get all heroes only
* @param {boolean} isAuthenticated - Flag to determine if hero profile should be fetched
* @returns {Promise<Array<Hero>>}
* @throws {InternalServerErrorException}
Expand All @@ -22,8 +19,7 @@ export class HeroService {
}

/**
* @description Get single heroes
* @implements Get single heroes with profile if authenticated, otherwise get single heroes only
* @description Get single heroes with profile if authenticated, otherwise get single heroes only
* @param {string} id - Hero ID
* @param {boolean} isAuthenticated - Flag to determine if hero profile should be fetched
* @returns {Promise<Array<Hero>>}
Expand Down

0 comments on commit 4e9430f

Please sign in to comment.