---
# System prepended metadata

title: Software Design

---


# Software Design

## 1. Technology

a. Programming Language: C# 12

b. FrameWork: .Net 8

c. Databases: PostgreSQL

d. Messaging/streaming platform: Pulsar

## 2. Development

a. Architecture: Clean

b. Branching strategy: Trunk-based development

c. ORM:

    i. Query: Dapper
    ii. Command: EF

d. Activity Diagrams -> 

d.1 OE-C1-UC-01: Request entities ordering strategy
![image](https://hackmd.io/_uploads/H1yDsnSwxg.png)


d.2 OE-C1-UC-02: Create entities ordering strategy
![image](https://hackmd.io/_uploads/B1UBgaBDxx.png)

d.3 OE-C1-UC-03: Apply ordering strategies to new entities

![image](https://hackmd.io/_uploads/ByucGTBDlg.png)

d.4 OE-C1-UC-04: Request applicable entities ordering strategy
![image](https://hackmd.io/_uploads/HyrdSpSPge.png)

d.5 
e. Data persistence:

    i. Technology: Redis

    ii. Cache type: Distributed

    iii. Cache updates strategy: OnDemand

    iv. Cache in circuit breaker (Returns cache data if we have issue returning data from persistent store): Yes / No -> An Bui

f. Invalidation strategy cache: Invalidate

g. Main patterns to use (Explain):

    i. Repository

        - The Repository acts as an intermediary layer between the Domain Layer and the Data Source, helping to:

        - Hide query details.

        - Reduce dependency on a specific ORM.

        - In DDD (Domain-Driven Design), the Repository should be implemented in the Infrastructure Layer so that the Domain Layer only uses POCOs (Plain Old Class Objects – a concept referring to simple classes that do not inherit from any specific framework base classes, are not dependent on external libraries or specifications, and usually do not contain complex business logic.)

        - Notes:
        
            + When using CQRS, Query Handlers should only use Repositories without managing transactions.
            
            + When communicating with a Repository: there should be an intermediate model layer, typically a DTO (Data Transfer Object), to act as a communication layer. This ensures that even if the domain model structure changes, it will not (or will only minimally) affect the rest of the system, reducing tight coupling.

    ii. UOW
    
        - Encapsulate and manage multiple Repositories within a single transaction.

        - Handle changes such as createDate, updateDate, etc.

        - Ensure data integrity and atomicity, especially when dealing with complex business logic.
        
        - Advantages:
        
            + Prevents data corruption or orphaned data caused by unsynchronized commits.

            + Simplifies scalability and control when performing write operations (Commands).

        - Note: Commonly used in Command Handlers, where necessary repositories are invoked using Lazy Loading to optimize performance.

    iii. CQRS
        
        - CQRS is a pattern that separates Commands (write operations) and Queries (read operations) in order to:

            + Increase clarity and independence between read and write processing flows.
            
            + Optimize system performance (reads and writes can scale independently).
        
            + Improve maintainability and make it easier to test individual pieces of logic.

        - Advantages: 
            
            + Enables complex logic handling in the write side without affecting the read side.

            + Enhances scalability: the read side can optimize queries, while the write side ensures data integrity.

            + Well-suited for systems following DDD (Domain-Driven Design) or microservices architecture.
            
        - When to Use?:
            
            + When there is a clear distinction between read and write logic.

            + When business logic is complex and write processing needs to be isolated.

            + When there’s a need to optimize query or write performance separately.

    iv. Mediator

        - MediatR is a library that implements the Mediator pattern, aiming to:

            + Separate the handling logic of commands/queries from controllers.

            + Send commands/queries through a Mediator as an intermediary, reducing dependencies between classes.
    
        - Advantages:
            
            + Keeps controllers clean and concise.

            + Increases modularity: easier to test and maintain.

            + Makes it easier to understand business logic by reading each individual command or query.

            + Can be extended with pipeline behaviors (e.g., logging, validation, etc.).

    v. Adapter

        - The Adapter Pattern is used to wrap external services (third-party) or services that cannot be modified, in order to:

            + Avoid direct dependency on the internal structure of the project.

            + Allow easy extension or replacement of services in the future without affecting the rest of the system.

        - Benefits:
        
            + Increases modularity and reusability.

            + Reduces tight coupling between the system and external libraries.

    -> kiểm tra thêm -> Tin Pham


h Entity API Endpoints


h.1 Get Ordering Criteria
Endpoint:
`GET /api/v1/ordering-criteriaIds?operatorId={operatorId}&localeId={localeId}&brandId={brandId}&offeringId={offeringId}`

Success Response (200 OK):
```json
{
  "isSuccess": true,
  "data": {
    "orderingCriteriaIds": [
        {
            "id": 1,
            "name": "Sports"
        },
        {
            "id": 2,
            "name": "Categoties (By Sport)"
        },
        {
            "id": 3,
            "name": "Tournaments (By Category)"
        }
    ],    
  "metadata": { "total": 3 },
  "error": null
}
``` 
h.2 Get Ordering Types
Endpoint:
`GET api/v1/ordering-type`
Success Response (200 OK):
```json
{
  "isSuccess": true,
  "data": {
    "orderingTypeIds": [
        {
            "id": 1,
            "name": "Alphabet"
        },
        {
            "id": 2,
            "name": "Custom"
        },
        {
            "id": 3,
            "name": "Trading"
        }
    ],    
  "metadata": { "total": 3 },
  "error": null
}
``` 

h.3 Get the Ordering Strategy

Endpoint: 
`GET /api/v1/ordering-strategies?operatorId={operatorId}&localeId={localeId}&brandId={brandId}&offeringId={offeringId}&orderingCriteriaId={orderingCriteriaId}&orderingTypeId={orderingTypeId}&sportId={sportId}&categoryId={categoryId}&marketGroupId={marketGroupId}`

Success Response (200 OK):

```json
{
  "isSuccess": true,
  "data": {
    "orderingStrategyId": 1,
    "orderingCriteriaId": 1, // Sports, Categoties, Tournaments, ... 
    "orderingTypeId": 2,
    "backfillOrderingTypeId": 1,
    "entities":  [
        {
            "id": 1,
            "name": "Basketball",
            "sortOrder": 1
        },
        {
            "id": 2,
            "name": "Tennis",
            "sortOrder": 2
        },
        {
            "id": 3,
            "name": "Football",
            "sortOrder": 3
        }
    ],
  },
  "metadata": { "total": 3 },
  "error": null
}
```

Success Response (204 OK):

```json
{
  "isSuccess": true,
  "data": [],
  "metadata": { "total": 0 },
  "error": null
}
```

h.4 Create entities ordering strategy

Endpoint: `POST /api/v1/ordering-strategies`

Request Body:
 - Body with orderingType as different custom

```json
{
    "operatorId": 1,
    "brandId": 1,
    "offeringId": 1,
    "localeId": 1,
    "orderingCriteriaId": 1, // Sports
    "orderingTypeId": 1, // Alphabetical
    "sportId":0,
    "categoryId":0
}
```
 - Body with orderingType as custom
```json
{
    "operatorId": 1,
    "brandId": 1,
    "offeringId": 1,
    "localeId": 1,
    "orderingCriteriaId": 1, // Sports
    "orderingTypeId": 2, // Custom
    "backfillOrderingTypeId": 1, // Alphabetical
    "entities": [
        {
            "id": 1,
            "sortOrder": 1
        },
        {
            "id": 3,
            "sortOrder": 1.5
        },
        {
            "id": 2,
            "sortOrder": 2
        }
    ]
}
```

Success Response (200 OK):

```json
  {   
    "isSuccess": true,
    "data": {
        "id": 1
    }
    "error": null
  }
```

h.5 Request applicable entities ordering strategy

Endpoint: `GET /api/v1/ordering-strategies/applicable-order?operatorId={operatorId}&localeId={localeId}&brandId={brandId}&offeringId={offeringId}&orderingCriteriaId={orderingCriteriaId}&orderingTypeId={orderingTypeId}&sportId={sportId}&categoryId={categoryId}&marketGroupId={marketGroupId}`

Success Response (200 OK):

```json
{
  "isSuccess": true,
  "data": {
    "operatorId": 1,
    "brandId": 1,
    "offeringId": 1,
    "localeId": 1,
    "orderingCriteriaId": 1, // Sports, Categoties, Tournaments, ... 
    "orderingTypeId": 2,
    "backfillOrderingTypeId": 1,
    "entities": [
        {
            "id": 3,
            "name": "Football",
            "sortOrder": 3
        },
        {
            "id": 5,
            "name": "Basketball",
            "sortOrder": 3.5
        },
        {
            "id": 4,
            "name": "Football",
            "sortOrder": 4
        }
    ]
  },
  "metadata": { "total": 3 },
  "error": null
}
```

h.6 Update entities ordering strategy

Endpoint: `PUT /api/v1/ordering-strategies/{id}`

Request Body:
 - Body with orderingType as different custom
```json
{
    "operatorId": 1,
    "brandId": 1,
    "offeringId": 1,
    "localeId": 1,
    "orderingCriteriaId": 1, // Sports
    "orderingTypeId": 1, // Alphabetical
}
```
 - Body with orderingType as custom
```json
{
    "operatorId": 1,
    "brandId": 1,
    "offeringId": 1,
    "localeId": 1,
    "orderingCriteriaId": 1, // Sports
    "orderingTypeId": 2, // Custom
    "backfillOrderingTypeId": 1, // Alphabetical
    "entities": [
        {
            "id": 1,
            "sortOrder": 1
        },
        {
            "id": 3,
            "sortOrder": 1.5
        },
        {
            "id": 2,
            "sortOrder": 2
        }
    ]
}
```

Success Response (200 OK):

```json
  {   
    "isSuccess": true,
    "data": {
        "id": 1
    }
    "error": null
  }
```

---

i Error Response Examples

i.1 400 Bad Request

```json
{
  "isSuccess": false,
  "error": {
    "title": "Validation Error",
    "description": "One or more validation errors occurred.",
    "type": "Validation",
    "details": [
      {
        "title": "Field.Validation",
        "description": "The value 'x' is not valid."
      },
      { "title": "Id.Required", "description": "The Id field is required." }
    ]
  }
}
```

i.2 401 Unauthorized

```json
{
  "isSuccess": false,
  "error": {
    "title": "Unauthorized",
    "description": "Authentication required.",
    "type": "Authentication",
    "details": []
  }
}
```

i.3 404 Not Found

```json
{
  "isSuccess": false,
  "error": {
    "title": "Not Found",
    "description": "The requested Entity was not found.",
    "type": "NotFound",
    "details": []
  }
}
```

i.4 405 Invalid Method 

```json
{
  "isSuccess": false,
  "error": {
    "title": "Invalid Method ",
    "description": "The method was invalid.",
    "type": "InvalidMethod",
    "details": []
  }
}
```

i.5 409 Conflict

```json
{
  "isSuccess": false,
  "error": {
    "title": "Conflict",
    "description": "The version was conflict.",
    "type": "Conflict",
    "details": []
  }
}
```

i.6 500 Internal Server Error

```json
{
  "isSuccess": false,
  "error": {
    "title": "Internal Server Error",
    "description": "An unexpected error occurred.",
    "type": "InternalError",
    "details": []
  }
}
```

i.7 503 Service Unavailable

```json
{
  "isSuccess": false,
  "error": {
    "title": "Service Unavailable",
    "description": "Service is unavailable.",
    "type": "ServiceUnavailable",
    "details": []
  }
}
```
---
k. Exception management:

i. Exception handling strategy: Global Excepttion handler

l. Data model definition -> Thuyen Nguyen

a. OrderingCriteria
###### a.1 Model
<table border="1">
  <tr>
    <th>Field</th>
    <th>Type</th>
    <th>Nullable</th>
    <th>Description</th>
  </tr>
  <tr>
    <td>Id</td>
    <td>long</td>
    <td>No</td>
    <td>Primary key</td>
  </tr>
  <tr>
    <td>Name</td>
    <td>string(100)</td>
    <td>No</td>
    <td></td>
  </tr>
  <tr>
    <td>Type</td>
    <td>Enum</td>
    <td>No</td>
    <td>
        Define enum
        <br/>
        1: Sports 
        <br/>
        2: Categoties (By Sport)
        <br/>
        3: Tournaments (By Category)
        <br/>
        4: Market Groups (By Sport)
        <br/>
        5: Market Types (By Market Group)
        <br/>
        6: Outright Groups (By Sport)
        <br/>
        7: Outright Types (By Sport)
    </td>
  </tr>
</table>

b. OrderingTypes
###### b.1 Model
<table border="1">
  <tr>
    <th>Field</th>
    <th>Type</th>
    <th>Nullable</th>
    <th>Description</th>
  </tr>
  <tr>
    <td>Id</td>
    <td>long</td>
    <td>No</td>
    <td>Primary key</td>
  </tr>
  <tr>
    <td>Name</td>
    <td>string(100)</td>
    <td>No</td>
    <td></td>
  </tr>
  <tr>
    <td>Type</td>
    <td>Enum</td>
    <td>No</td>
    <td>
        Define enum
        <br/>
        1: Alphabetical 
        <br/>
        2: Custom
        <br/>
        3: Trading
        <br/>
        4: Most Popular
    </td>
  </tr>
</table>

c. OrderingStrategies
###### c.1 Model
<table border="1">
  <tr>
    <th>Field</th>
    <th>Type</th>
    <th>Nullable</th>
    <th>Description</th>
  </tr>
  <tr>
    <td>Id</td>
    <td>long</td>
    <td>No</td>
    <td>Primary key</td>
  </tr>
  <tr>
    <td>OperatorId</td>
    <td>int</td>
    <td>No</td>
    <td>Referencing the Operator table</td>
  </tr>
  <tr>
    <td>BrandId</td>
    <td>int</td>
    <td>Yes</td>
    <td>Referencing the Brand table</td>
  </tr>
  <tr>
    <td>OfferingId</td>
    <td>int</td>
    <td>Yes</td>
    <td>Referencing the Offering table</td>
  </tr>
  <tr>
    <td>LocaleId</td>
    <td>int</td>
    <td>No</td>
    <td>Referencing the Countries table</td>
  </tr>
  <tr>
    <td>OrderingCriteriaId</td>
    <td>long</td>
    <td>No</td>
    <td>Foreign key referencing the OrderingCriteria table</td>
  </tr>
  <tr>
    <td>OrderingTypeId</td>
    <td>long</td>
    <td>No</td>
    <td>Foreign key referencing the OrderingTypes table</td>
  </tr>
  <tr>
    <td>BackfillOrderingTypeId</td>
    <td>long</td>
    <td>Yes</td>
    <td>Referencing the OrderingTypes table</td>
  </tr>
  <tr>
    <td>CreatedAt</td>
    <td>DateTime</td>
    <td>No</td>
    <td>Default system time</td>
  </tr>
  <tr>
    <td>CreatedBy</td>
    <td>long</td>
    <td>No</td>
    <td>Initial creator</td>
  </tr>
  <tr>
    <td>UpdatedAt</td>
    <td>DateTime</td>
    <td>Yes</td>
    <td>Last modified date</td>
  </tr>
  <tr>
    <td>UpdatedBy</td>
    <td>long</td>
    <td>Yes</td>
    <td>Last modifier</td>
  </tr>
</table>

###### c.2 Index
<table border="1">
  <tr>
    <th>Field</th>
    <th>Index name</th>
  </tr>
  <tr>
    <td>Id</td>
    <td>Primary</td>
  </tr>
  <tr>
    <td>OperatorId</td>
    <td>IX_OrderingStrategies_OperatorId</td>
  </tr>
  <tr>
    <td>LocaleId</td>
    <td>IX_OrderingStrategies_LocaleId</td>
  </tr>
  <tr>
    <td>OrderingCriteriaId</td>
    <td>IX_OrderingStrategies_OrderingCriteriaId</td>
  </tr>
  <tr>
    <td>OrderingTypeId</td>
    <td>IX_OrderingStrategies_OrderingTypeId</td>
  </tr>
</table>

d. EntityOrderings

###### d.1 Model
<table border="1">
  <tr>
    <th>Field</th>
    <th>Type</th>
    <th>Nullable</th>
    <th>Description</th>
  </tr>
  <tr>
    <td>Id</td>
    <td>long</td>
    <td>No</td>
    <td>Primary key</td>
  </tr>
  <tr>
    <td>OrderingStrategyId</td>
    <td>long</td>
    <td>No</td>
    <td>Foreign key referencing the OrderingStrategies table</td>
  </tr>
  <tr>
    <td>EntityId</td>
    <td>long</td>
    <td>No</td>
    <td></td>
  </tr>
  <tr>
    <td>EntityCode</td>
    <td>string(10)</td>
    <td>No</td>
    <td></td>
  </tr>
  <tr>
    <td>EntityName</td>
    <td>string(50)</td>
    <td>No</td>
    <td></td>
  </tr>
  <tr>
    <td>SortOrder</td>
    <td>decimal(10,4)</td>
    <td>No</td>
    <td></td>
  </tr> 
  <tr>
    <td>OrderingRole</td>
    <td>Enum</td>
    <td>Yes</td>
    <td>
        Only applies to Ordering Type as <strong>Custom</strong>, to distinguish custom and backfill values
        <br/>
        Define enum:
        <br/>
        1: Custom ordering
        <br/>
        2: Backfill ordering
      </td>
  </tr>
  <tr>
    <td>CreatedAt</td>
    <td>DateTime</td>
    <td>No</td>
    <td>Default system time</td>
  </tr>
  <tr>
    <td>CreatedBy</td>
    <td>long</td>
    <td>No</td>
    <td>Initial creator</td>
  </tr>
  <tr>
    <td>UpdatedAt</td>
    <td>DateTime</td>
    <td>Yes</td>
    <td>Last modified date</td>
  </tr>
  <tr>
    <td>UpdatedBy</td>
    <td>long</td>
    <td>Yes</td>
    <td>Last modifier</td>
  </tr>
</table>

###### d.2 Index
<table border="1">
  <tr>
    <th>Field</th>
    <th>Index name</th>
  </tr>
  <tr>
    <td>Id</td>
    <td>Primary</td>
  </tr>
  <tr>
    <td>OrderingStrategyId, SortOrder</td>
    <td>IX_EntityOrderings_OrderingStrategyId_SortOrder</td>
  </tr>
    <tr>
<td>EntityId</td>
        <td>IX_EntityOrderings_EntityId</td>
</tr>
</table>

###### e.1 AuditLogs
<table border="1">
  <tr>
    <th>Field</th>
    <th>Type</th>
    <th>Nullable</th>
    <th>Description</th>
  </tr>
  <tr>
    <td>Id</td>
    <td>long</td>
    <td>No</td>
    <td>Primary key</td>
  </tr>
  <tr>
    <td>EntityType</td>
    <td>Enum</td>
    <td>No</td>
    <td>
        Define enum: 1 Strategy
      </td>
  </tr>
  <tr>
    <td>EntityId</td>
    <td>long</td>
    <td>No</td>
    <td></td>
  </tr>
  <tr>
    <td>AuditAction</td>
    <td>Enum</td>
    <td>No</td>
    <td>Create / Update / Delete</td>
  </tr>
  <tr>
    <td>Snapshot</td>
    <td>string (json)</td>
    <td>No</td>
    <td></td>
  </tr>
</table>

## 3. External dependencies

a. External Libraries: -> Tin Pham

    Coding Project:

        - Swagger: Swashbuckle.AspNetCore

        - API Versioning: Asp.Versioning.Http, Asp.Versioning.Mvc.ApiExplorer

        - System Healthcheck: HealthChecks.UI, AspNetCore.HealthChecks.UI.Client

        - Mediator pattern: MediatR
        
        - Request Model Validator: FluentValidation

        - Logging: Microsoft.Extensions.Logging.Abstractions

        - Tracing and Monitoring: NewRelic.Agent.Api
        
        - Caching: MicrosoftExtension.Redis

        - PostgreSQL Healthcheck: AspNetCore.HealthChecks.NpgSql

        - Redis Cache Healthcheck: AspNetCore.HealthChecks.Redis
    
    Testing Project:
        
        - Unit Test: NUnit, NUnit.Analyzers, NUnit.Runners, NUnit3TestAdapter
        
        - Fluent assertion library: Shouldy
        
        - Mocking: FakeItEasy
        
        - Test coverage tool: coverlet.collector, coverlet.msbuild
        
        - Component Test: Testcontainers, Testcontainers.Keycloak, Testcontainers.PostgreSql, Testcontainers.Pulsar, Testcontainers.Redis

        - BDD Testing: Reqnroll, Reqnroll.NUnit, Reqnroll.Microsoft.Extensions.DependencyInjection
    

b. External services: No

## 4. Testing

a. Unit Testing

    i. Tools: NUnit

    ii. Coverage: 90%

b. Component Testing

    i. Tools: Test Containers

    ii. FrameWork: ReqnRoll

c. Integration Testing

    i. Tools: Mock API -> kiểm tra lại -> Anh Trinh -> Cypress
    - MockAPI được sử dụng cho Component Test. Dùng Wiremock để định nghĩa các endpoint, Component sẽ gọi tới endpoint giả lập (mocked) thay vì gọi tới API thật.
    - Integration Testing sử dụng môi trường thật nên thường không cần dùng mockAPI

    ii. FrameWork: ReqnRoll

    iii. Systems to confirm the correct integration -> tìm hiểu thêm -> Hong Nguyen
    - Frontend ↔️ Backend: Cypress UI gọi đúng API và nhận phản hồi chuẩn
    - Backend ↔️ Database: Kiểm tra lưu/truy xuất dữ liệu qua API và thực hiện tạo/dọn dữ liệu test trước và sau mỗi lần chạy test bằng Knex.js
    - Microservice ↔️ Keycloak: Xác thực bằng token đăng nhập hoạt động đúng trong các luồng test

d. Stress test

    i. Tools: XK6

e. Test plan

    i. Test scenarios for the use cases defined by product? -> Thuy Vo, Hanh Ma
    - 1 Usecase tương ứng 1 Test scenarios
    - 1 Test Scenarios có thể có nhiều test cases
    - Với auto test, sẽ có 1 test plan cho cả 1 dự án(cover tất cả use cases được define)
    ii. Acceptance criteria for Stress test -> Bang Nguyen
    - Dựa vào requirement của từng component, có expected throughput do bên product define
    - Đảm bảo hệ thống có thể chịu tải được hơn ngưỡng expected 1 chút