Skip to content

Commit

Permalink
refactor: add DI container and refactor student repository
Browse files Browse the repository at this point in the history
  • Loading branch information
arahiko-ayami committed Jan 9, 2024
1 parent 35a69b4 commit ec10c14
Show file tree
Hide file tree
Showing 28 changed files with 233 additions and 110 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ This project is structured as a monorepo and based on [Domain Driven Design (DDD

```
├── packages
│ ├── core - Contains all of your business logic following clean architecture and domain driven design/
│ ├── core - Contains all of your business logic following clean architecture and domain driven design
│ │ └── src
│ │ ├── application - Define behaviour of the application, interact with services from outside packages/core
│ │ │ ├── ports - Define contracts interfaces that should be honoured by the infrastructure.
Expand All @@ -30,19 +30,19 @@ This project is structured as a monorepo and based on [Domain Driven Design (DDD
### 📜 Presiquites

- [Node.js](https://nodejs.org/en/) (v18 or higher)
- [npm](https://www.npmjs.com/) (v8 or higher)
- [pnpm](https://pnpm.io/) (v8 or higher)
- [AWS CLI](https://aws.amazon.com/cli/) (v2 or higher)

### 📦 Installation

```bash
$ npm i
$ pnpm i
```

### 🏃‍♂️ Run

```bash
$ npm run dev
$ pnpm dev
```

### 🚀 Deploy
Expand All @@ -55,7 +55,7 @@ $ npm run dev
#### Deploy

```bash
$ npm run deploy --stage [stage_name]
$ pnpm deploy --stage [stage_name]
```

## 📝 License
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/application/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
import 'reflect-metadata';

export * from './prisma';
export * from './util';
2 changes: 2 additions & 0 deletions packages/core/src/application/ports/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './subject';
export * from './student';
export * from './shared';
1 change: 1 addition & 0 deletions packages/core/src/application/ports/score/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './interfaces';
8 changes: 8 additions & 0 deletions packages/core/src/application/ports/score/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Score } from '@prisma/client';
import { LearningResult } from '../subject';
import { GetById } from '@infra/student';

export interface ICalculateScoreService {
getLearningResult(scores: Score[]): LearningResult;
getAverageScore(student: GetById): number;
}
1 change: 1 addition & 0 deletions packages/core/src/application/ports/shared/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './interfaces';
1 change: 1 addition & 0 deletions packages/core/src/application/ports/shared/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export interface ExpandFields {}
1 change: 1 addition & 0 deletions packages/core/src/application/ports/student/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './interfaces';
13 changes: 13 additions & 0 deletions packages/core/src/application/ports/student/interfaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { StudentQueryResult } from '@infra/index';
import { ExpandFields } from '../shared';

export interface StudentExpandFields extends ExpandFields {
withScores?: boolean;
}

export interface IStudentRepository {
getById(
id: string,
expandFields?: StudentExpandFields,
): Promise<StudentQueryResult>;
}
12 changes: 12 additions & 0 deletions packages/core/src/container/container.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Container from 'typedi';
import { Cradle } from './cradle';
import { CalculateScoreService } from '@infra/score';
import { StudentRepository } from '@infra/student';

export const container: Cradle = {
// Services
calculateScoreService: Container.get(CalculateScoreService),

// Repositories
studentRepository: Container.get(StudentRepository),
};
9 changes: 9 additions & 0 deletions packages/core/src/container/cradle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { CalculateScoreService } from '@infra/score';
import { StudentRepository } from '@infra/student';

export interface Cradle {
// Services
calculateScoreService: CalculateScoreService;
// Repositories
studentRepository: StudentRepository;
}
2 changes: 2 additions & 0 deletions packages/core/src/container/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './container';
export * from './cradle';
4 changes: 4 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import 'reflect-metadata';

export * from './application';
export * from './domain';
export * from './infra';
export * from './shared';
export * from './container';
96 changes: 49 additions & 47 deletions packages/core/src/infra/score/calculateScoreService.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,56 @@
import { LearningResult } from '@application/ports';
import { isPassedSubject, shouldCalculateScore } from '@application/util';
import { AlphabetToTetraScore } from '@domain/index';
import { StudentWithScoresAndSubjects } from '@infra/index';
import { StudentQueryResult } from '@infra/index';
import { Score } from '@prisma/client';
import { Service } from 'typedi';

@Service()
export class CalculateScoreService {
getLearningResult(scores: Score[]): LearningResult {
const passed = scores.filter((score) => isPassedSubject(score)).length;
const failed = scores.length - passed;

return {
passed,
failed,
};
}

getAverageScore(student?: StudentQueryResult): number {
if (!student) throw new Error('Student not found');

const { scores } = student;

const totalAverageScore = scores.reduce((acc, score) => {
if (shouldCalculateScore(score)) {
if (
!score.alphabetScore ||
!(score.alphabetScore in AlphabetToTetraScore)
) {
return acc;
}

return (
acc +
// * This is an enum so it's safe to ignore this
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
AlphabetToTetraScore[score.alphabetScore] *
score.subject.numberOfCredits
);
}
return acc;
}, 0);

export const getLearningResult = (scores: Score[]): LearningResult => {
const passed = scores.filter((score) => isPassedSubject(score)).length;
const failed = scores.length - passed;

return {
passed,
failed,
};
};

export const getAverageScore = (
student?: StudentWithScoresAndSubjects,
): number => {
if (!student) throw new Error('Student not found');

const { scores } = student;

const totalAverageScore = scores.reduce((acc, score) => {
if (shouldCalculateScore(score)) {
if (
!score.alphabetScore ||
!(score.alphabetScore in AlphabetToTetraScore)
) {
return acc;
const totalCredits = scores.reduce((acc, score) => {
if (shouldCalculateScore(score)) {
return acc + score.subject.numberOfCredits;
}
return acc;
}, 0);

return (
acc +
// * This is an enum so it's safe to ignore this
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
AlphabetToTetraScore[score.alphabetScore] *
score.subject.numberOfCredits
);
}
return acc;
}, 0);

const totalCredits = scores.reduce((acc, score) => {
if (shouldCalculateScore(score)) {
return acc + score.subject.numberOfCredits;
}
return acc;
}, 0);

const averageScore = totalAverageScore / totalCredits;
return Number(averageScore.toFixed(2));
};
const averageScore = totalAverageScore / totalCredits;
return Number(averageScore.toFixed(2));
}
}
12 changes: 12 additions & 0 deletions packages/core/src/infra/student/include.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Prisma } from '@prisma/client';

export const studentIncludeScoreAndSubject =
Prisma.validator<Prisma.StudentDefaultArgs>()({
include: {
scores: {
include: {
subject: true,
},
},
},
});
2 changes: 1 addition & 1 deletion packages/core/src/infra/student/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from './studentPersistence';
export * from './studentRepository';
export * from './types';
21 changes: 0 additions & 21 deletions packages/core/src/infra/student/studentPersistence.ts

This file was deleted.

33 changes: 33 additions & 0 deletions packages/core/src/infra/student/studentRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { prisma } from '@application/index';
import { IStudentRepository, StudentExpandFields } from '@application/ports';
import { Prisma } from '@prisma/client';
import { Repository } from 'shared';
import { StudentQueryResult } from './types';

@Repository()
export class StudentRepository implements IStudentRepository {
async getById(id: string, expandFields: StudentExpandFields) {
// TODO: Implement a better solution for dynamic include
const include: Prisma.StudentInclude = {
scores: expandFields.withScores
? {
include: {
subject: true,
},
where: {
studentId: id,
},
}
: {},
};

const student = await prisma.student.findUnique({
include,
where: {
id: id,
},
});

return student as unknown as StudentQueryResult;
}
}
12 changes: 9 additions & 3 deletions packages/core/src/infra/student/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { Prisma } from '@prisma/client';
import { getStudentWithScoreById } from '@infra/student';
import { studentIncludeScoreAndSubject } from './include';

export type StudentWithScoresAndSubjects = Prisma.PromiseReturnType<
typeof getStudentWithScoreById
/**
* * This is a type that is used to dynamically include the fields of the student
* * This will resolve type error when we use the `include` option of Prisma dynamically
* TODO: Remove this when Prisma support dynamic include types or when we have better solution
* https://github.com/prisma/prisma/issues/20816#issuecomment-1873546485
*/
export type StudentQueryResult = Prisma.StudentGetPayload<
typeof studentIncludeScoreAndSubject
>;
1 change: 1 addition & 0 deletions packages/core/src/shared/decorators/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './repository';
9 changes: 9 additions & 0 deletions packages/core/src/shared/decorators/repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ClassConstructor } from 'shared/types';
import { Service } from 'typedi';

export function Repository<T>() {
return function (target: ClassConstructor<T>) {
// Apply the TypeDI Service decorator
Service()(target);
};
}
2 changes: 2 additions & 0 deletions packages/core/src/shared/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './decorators';
export * from './types';
2 changes: 2 additions & 0 deletions packages/core/src/shared/types/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type ClassConstructor<T> = new (...args: any[]) => T;
2 changes: 2 additions & 0 deletions packages/core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
"extends": "@tsconfig/node18/tsconfig.json",
"compilerOptions": {
"module": "esnext",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution": "node",
"baseUrl": "src",
"paths": {
Expand Down
4 changes: 3 additions & 1 deletion packages/functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
"@middy/core": "^4.5.5",
"@middy/http-error-handler": "^4.5.5",
"@middy/http-json-body-parser": "^4.5.5",
"@middy/validator": "^4.5.5"
"@middy/validator": "^4.5.5",
"reflect-metadata": "^0.2.1",
"typedi": "^0.10.0"
}
}
Loading

0 comments on commit ec10c14

Please sign in to comment.