From d5de65fdb1802cdf498d65d93397f290813c377c Mon Sep 17 00:00:00 2001 From: muqiuhan Date: Tue, 9 Sep 2025 06:17:00 +0000 Subject: deploy: 9665097f0fa0dae9f92123fac54d26c0818758a5 --- .../index.html" | 14 +++++++----- .../index.html" | 20 ++++++++++++++++- .../index.html" | 26 ++++++++++------------ .../19/Repository-pattern-in-Typescript/index.html | 16 ++++++------- 4 files changed, 46 insertions(+), 30 deletions(-) (limited to '2025/02') diff --git "a/2025/02/10/\345\256\236\346\227\266\346\220\234\347\264\242\344\270\255\347\232\204\351\230\262\346\212\226\345\207\275\346\225\260/index.html" "b/2025/02/10/\345\256\236\346\227\266\346\220\234\347\264\242\344\270\255\347\232\204\351\230\262\346\212\226\345\207\275\346\225\260/index.html" index 4f9ecd38..2390de3e 100644 --- "a/2025/02/10/\345\256\236\346\227\266\346\220\234\347\264\242\344\270\255\347\232\204\351\230\262\346\212\226\345\207\275\346\225\260/index.html" +++ "b/2025/02/10/\345\256\236\346\227\266\346\220\234\347\264\242\344\270\255\347\232\204\351\230\262\346\212\226\345\207\275\346\225\260/index.html" @@ -193,18 +193,20 @@

在实现实时搜索功能时,通常会使用输入框的事件监听器来捕获用户的输入变化,并在输入变化时发送搜索请求。为了避免过多的请求导致服务器负担过重,通常会使用“防抖”(debounce)技术来控制请求的频率。

-

实现步骤

    +

    实现步骤

    +
    1. 监听输入框的变化:使用input事件监听器来捕获用户的输入变化。
    2. 防抖处理:使用防抖函数来限制请求的频率。防抖函数会在用户停止输入一段时间后才发送请求。
    3. 发送请求:在防抖函数中调用搜索请求。
    -

    防抖函数示例

    以下是一个简单的防抖函数示例:

    +

    防抖函数示例

    +

    以下是一个简单的防抖函数示例:

    1
    2
    3
    4
    5
    6
    7
    8
    function debounce(func, wait) {
    let timeout;
    return function(...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
    };
    }
    - -

    实现实时搜索

    假设你有一个输入框用于搜索患者:

    +

    实现实时搜索

    +

    假设你有一个输入框用于搜索患者:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <script>
    import { onMount } from 'svelte';
    import { patientsStore } from '$lib/stores/patients.svelte';

    let searchTerm = '';

    // 防抖函数
    function debounce(func, wait) {
    let timeout;
    return function(...args) {
    const context = this;
    clearTimeout(timeout);
    timeout = setTimeout(() => func.apply(context, args), wait);
    };
    }

    // 搜索函数
    const searchPatients = debounce(async (term) => {
    if (term) {
    // 发送搜索请求
    const response = await fetch(`/api/search-patients?query=${term}`);
    const data = await response.json();
    patientsStore.mbglPatients = data;
    } else {
    // 清空搜索结果或恢复默认数据
    patientsStore.mbglPatients = [];
    }
    }, 300); // 300ms 的防抖时间

    // 监听输入框变化
    function handleInput(event) {
    searchTerm = event.target.value;
    searchPatients(searchTerm);
    }
    </script>

    <input type="text" placeholder="搜索患者..." on:input={handleInput} bind:value={searchTerm} />
    - -

    请求发送间隔

      +

      请求发送间隔

      +
      • 防抖时间:通常设置为 300ms 到 500ms 之间。这个时间足够让用户完成输入并减少不必要的请求。
      • 考虑用户体验:防抖时间过短可能导致过多请求,过长则可能让用户感到延迟。300ms 是一个常用的折中值。
      diff --git "a/2025/02/11/\344\273\216\344\272\213\344\273\266\351\243\216\346\232\264\347\234\213\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/index.html" "b/2025/02/11/\344\273\216\344\272\213\344\273\266\351\243\216\346\232\264\347\234\213\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/index.html" index 7afb6836..3ef557ea 100644 --- "a/2025/02/11/\344\273\216\344\272\213\344\273\266\351\243\216\346\232\264\347\234\213\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/index.html" +++ "b/2025/02/11/\344\273\216\344\272\213\344\273\266\351\243\216\346\232\264\347\234\213\351\242\206\345\237\237\351\251\261\345\212\250\350\256\276\350\256\241/index.html" @@ -194,7 +194,25 @@

      事件风暴(Event Storming)是一种领域驱动设计(DDD)的实践方法,由 Alberto Brandolini 提出,旨在通过团队协作的方式快速理解和建模业务领域。

      以下是事件风暴及相关领域驱动设计中的一些核心概念和知识点:

      -

      1. 领域(Domain):指的是业务相关知识的集合,可以进一步划分为子域。
      2. 子域(Subdomain):是领域的一部分,可以是核心域、支撑域或通用域。
      3. 核心域(Core Domain):指领域中最核心的部分,通常对应企业的核心业务。
      4. 通用语言(Ubiquitous Language):团队所有成员使用的一种语言,用于确保业务和软件之间的沟通一致性。
      5. 限界上下文(Bounded Context):定义了一组规则和协议,用于明确领域模型的适用范围。
      6. 实体(Entity):具有唯一标识和生命周期的领域对象。
      7. 值对象(Value Object):描述了某种特性或属性的对象,没有概念标识。
      8. 聚合(Aggregate):一组相关对象的集合,由一个聚合根(Aggregate Root)统一管理。
      9. 领域事件(Domain Event):领域中发生的重要事件,可以用于通知其他领域对象或跨限界上下文进行解耦和协作。
      10. 命令(Command):表示要执行的操作,通常与事件一一对应。
      11. 读模型(Read Model):为了优化读取操作而设计的模型,可能与写模型不同。
      12. 决策命令(Decision Command):在事件风暴中,直接导致事件发生的命令。
      13. 战略设计(Strategic Design):高层次的抽象和归类,包括理清上下文和子域的划分。
      14. 战术设计(Tactical Design):对特定上下文下的模型进行详细设计,包括聚合、实体和值对象。
      15. 贫血模型(Anemic Domain Model):领域对象只有属性及其getter/setter方法的纯数据类,业务逻辑通过服务实现。
      16. 充血模型(Rich Domain Model):领域对象包含业务逻辑,每个对象都是活跃的。
      17. 资源库(Repository):用于检索和持久化领域对象的机制。
      18. 服务(Service):在模型中独立的操作,可以是领域服务或应用服务。
      19. 固定规则(Invariant):为设计元素做出的断言,必须一直保持为真。

      +

      1. 领域(Domain):指的是业务相关知识的集合,可以进一步划分为子域。
      +2. 子域(Subdomain):是领域的一部分,可以是核心域、支撑域或通用域。
      +3. 核心域(Core Domain):指领域中最核心的部分,通常对应企业的核心业务。
      +4. 通用语言(Ubiquitous Language):团队所有成员使用的一种语言,用于确保业务和软件之间的沟通一致性。
      +5. 限界上下文(Bounded Context):定义了一组规则和协议,用于明确领域模型的适用范围。
      +6. 实体(Entity):具有唯一标识和生命周期的领域对象。
      +7. 值对象(Value Object):描述了某种特性或属性的对象,没有概念标识。
      +8. 聚合(Aggregate):一组相关对象的集合,由一个聚合根(Aggregate Root)统一管理。
      +9. 领域事件(Domain Event):领域中发生的重要事件,可以用于通知其他领域对象或跨限界上下文进行解耦和协作。
      +10. 命令(Command):表示要执行的操作,通常与事件一一对应。
      +11. 读模型(Read Model):为了优化读取操作而设计的模型,可能与写模型不同。
      +12. 决策命令(Decision Command):在事件风暴中,直接导致事件发生的命令。
      +13. 战略设计(Strategic Design):高层次的抽象和归类,包括理清上下文和子域的划分。
      +14. 战术设计(Tactical Design):对特定上下文下的模型进行详细设计,包括聚合、实体和值对象。
      +15. 贫血模型(Anemic Domain Model):领域对象只有属性及其getter/setter方法的纯数据类,业务逻辑通过服务实现。
      +16. 充血模型(Rich Domain Model):领域对象包含业务逻辑,每个对象都是活跃的。
      +17. 资源库(Repository):用于检索和持久化领域对象的机制。
      +18. 服务(Service):在模型中独立的操作,可以是领域服务或应用服务。
      +19. 固定规则(Invariant):为设计元素做出的断言,必须一直保持为真。

      事件风暴通常包括以下步骤:

      • 识别领域事件
      • diff --git "a/2025/02/18/Prisma-\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\347\232\204-Self-relations/index.html" "b/2025/02/18/Prisma-\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\347\232\204-Self-relations/index.html" index 0eac083b..0916a06e 100644 --- "a/2025/02/18/Prisma-\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\347\232\204-Self-relations/index.html" +++ "b/2025/02/18/Prisma-\345\205\263\347\263\273\345\236\213\346\225\260\346\215\256\345\272\223\347\232\204-Self-relations/index.html" @@ -195,8 +195,8 @@

        A relation field can also reference its own model, in this case the relation is called a self-relation. Self-relations can be of any cardinality, 1-1, 1-n and m-n.

        -

        一对一

        1
        2
        3
        4
        5
        6
        7
        model User {
        id Int @id @default(autoincrement())
        name String?
        successorId Int? @unique
        successor User? @relation("BlogOwnerHistory", fields: [successorId], references: [id])
        predecessor User? @relation("BlogOwnerHistory")
        }
        - +

        一对一

        +
        1
        2
        3
        4
        5
        6
        7
        model User {
        id Int @id @default(autoincrement())
        name String?
        successorId Int? @unique
        successor User? @relation("BlogOwnerHistory", fields: [successorId], references: [id])
        predecessor User? @relation("BlogOwnerHistory")
        }

        User 展现了这样一个模型:

        • User 可以有一个或零个前驱(predecessor)
        • @@ -214,9 +214,8 @@

          而在关系型数据库中,一对一的 self-relation 可以用如下 SQL 描述:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          CREATE TABLE "User" (
          id SERIAL PRIMARY KEY,
          "name" TEXT,
          "successorId" INTEGER
          );

          ALTER TABLE "User" ADD CONSTRAINT fk_successor_user FOREIGN KEY ("successorId") REFERENCES "User" (id);

          ALTER TABLE "User" ADD CONSTRAINT successor_unique UNIQUE ("successorId");
          - -

          一对多

          1
          2
          3
          4
          5
          6
          7
          model User {
          id Int @id @default(autoincrement())
          name String?
          teacherId Int?
          teacher User? @relation("TeacherStudents", fields: [teacherId], references: [id])
          students User[] @relation("TeacherStudents")
          }
          - +

          一对多

          +
          1
          2
          3
          4
          5
          6
          7
          model User {
          id Int @id @default(autoincrement())
          name String?
          teacherId Int?
          teacher User? @relation("TeacherStudents", fields: [teacherId], references: [id])
          students User[] @relation("TeacherStudents")
          }

          User 展现了这样一个模型:

          • 一个 User 只能有零个或一个 teacher
          • @@ -227,27 +226,26 @@

            用 SQL 描述 User model:

            1
            2
            3
            4
            5
            6
            7
            CREATE TABLE "User" (
            id SERIAL PRIMARY KEY,
            "name" TEXT,
            "teacherId" INTEGER
            );

            ALTER TABLE "User" ADD CONSTRAINT fk_teacherid_user FOREIGN KEY ("teacherId") REFERENCES "User" (id);
            -

            teacherId 没有使用 UNIQUE 约束,这代表着多个 students 可以有同一个 teacher

            -

            多对多

            1
            2
            3
            4
            5
            6
            model User {
            id Int @id @default(autoincrement())
            name String?
            followedBy User[] @relation("UserFollows")
            following User[] @relation("UserFollows")
            }
            - +

            多对多

            +
            1
            2
            3
            4
            5
            6
            model User {
            id Int @id @default(autoincrement())
            name String?
            followedBy User[] @relation("UserFollows")
            following User[] @relation("UserFollows")
            }
            • 一个 User 可以被零个或多个 Users 关注
            • 一个 User 可以关注零个或多个 Users
            -

            对于关系型数据库,多对多的关系是隐式的,这意味着 Prisma ORM 会在底层数据库中维护一个 relation table:
            A relation table (also sometimes called a JOIN, link or pivot table) connects two or more other tables and therefore creates a relation between them. Creating relation tables is a common data modelling practice in SQL to represent relationships between different entities. In essence it means that “one m-n relation is modeled as two 1-n relations in the database”.

            +

            对于关系型数据库,多对多的关系是隐式的,这意味着 Prisma ORM 会在底层数据库中维护一个 relation table:
            +A relation table (also sometimes called a JOIN, link or pivot table) connects two or more other tables and therefore creates a relation between them. Creating relation tables is a common data modelling practice in SQL to represent relationships between different entities. In essence it means that “one m-n relation is modeled as two 1-n relations in the database”.

            We recommend using implicit m-n-relations, where Prisma ORM automatically generates the relation table in the underlying database. Explicit m-n-relations should be used when you need to store additional data in the relations, such as the date the relation was created.

            如果需要需要通过多对多的关系来保存其他字段,也可以创建显式的多对多 self 关系:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            model User {
            id Int @id @default(autoincrement())
            name String?
            followedBy Follows[] @relation("followedBy")
            following Follows[] @relation("following")
            }

            model Follows {
            followedBy User @relation("followedBy", fields: [followedById], references: [id])
            followedById Int
            following User @relation("following", fields: [followingId], references: [id])
            followingId Int

            @@id([followingId, followedById])
            }
            -

            在关系型数据库中,可以用如下 SQL 描述:

            1
            2
            3
            4
            5
            6
            7
            8
            CREATE TABLE "User" (
            id integer DEFAULT nextval('"User_id_seq"'::regclass) PRIMARY KEY,
            name text
            );
            CREATE TABLE "_UserFollows" (
            "A" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE,
            "B" integer NOT NULL REFERENCES "User"(id) ON DELETE CASCADE ON UPDATE CASCADE
            );
            - -

            在同一模型上建立多个 self-relations

            1
            2
            3
            4
            5
            6
            7
            8
            9
            model User {
            id Int @id @default(autoincrement())
            name String?
            teacherId Int?
            teacher User? @relation("TeacherStudents", fields: [teacherId], references: [id])
            students User[] @relation("TeacherStudents")
            followedBy User[] @relation("UserFollows")
            following User[] @relation("UserFollows")
            }
            - -

            REFS.

              +

              在同一模型上建立多个 self-relations

              +
              1
              2
              3
              4
              5
              6
              7
              8
              9
              model User {
              id Int @id @default(autoincrement())
              name String?
              teacherId Int?
              teacher User? @relation("TeacherStudents", fields: [teacherId], references: [id])
              students User[] @relation("TeacherStudents")
              followedBy User[] @relation("UserFollows")
              following User[] @relation("UserFollows")
              }
              +

              REFS.

              +
              • fully annotated
              • required
              • implicit
              • diff --git a/2025/02/19/Repository-pattern-in-Typescript/index.html b/2025/02/19/Repository-pattern-in-Typescript/index.html index c0753a94..99250448 100644 --- a/2025/02/19/Repository-pattern-in-Typescript/index.html +++ b/2025/02/19/Repository-pattern-in-Typescript/index.html @@ -198,30 +198,28 @@

                抽象化数据访问层的主要作用是将应用的业务逻辑与对数据库的数据访问等实现细节进行解耦。DB 框架的更改不应该影响到应用程序的核心服务,并且它们应该对业务逻辑代码透明。

                业务服务应该只依赖于抽象,而不是实现,数据服务同理。


                -

                Intro.

                例如,此时有一个书籍存储的微服务,并且有一个“添加新书籍”的操作用例,在此用例中可以通过使用 Book 这个 Repository 来添加新书籍,而 Book 具体使用什么数据库,怎么存并不是业务需要关心的事情。

                -

                具体实现方式

                假设有以下实体类型定义:

                +

                Intro.

                +

                例如,此时有一个书籍存储的微服务,并且有一个“添加新书籍”的操作用例,在此用例中可以通过使用 Book 这个 Repository 来添加新书籍,而 Book 具体使用什么数据库,怎么存并不是业务需要关心的事情。

                +

                具体实现方式

                +

                假设有以下实体类型定义:

                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                12
                13
                14
                15
                export class Author {
                firstName: string;
                lastName: string;
                }

                export class Genre {
                name: string;
                }

                export class Book {
                title: string;
                author: Author;
                genre: Genre;
                publishDate: Date;
                }
                - -

                抽象化

                第一步是抽象化出一个通用的 Repository ,其中包含通用的操作:

                +

                抽象化

                +

                第一步是抽象化出一个通用的 Repository ,其中包含通用的操作:

                1
                2
                3
                4
                5
                6
                7
                8
                9
                export abstract class IGenericRepository<T> {
                abstract getAll(): Promise<T[]>;

                abstract get(id: string): Promise<T>;

                abstract create(item: T): Promise<T>;

                abstract update(id: string, item: T);
                }
                -
                • 这里具体有那些函数可以根据需要(业务)自己定义。
                • 类型参数 T 表示每个实体。
                -

                接着,定义一个数据服务,其中包含使用 IGenericRepositoiry 定义的具体实例对应的 _Repository_:

                +

                接着,定义一个数据服务,其中包含使用 IGenericRepositoiry 定义的具体实例对应的 Repository:

                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                import { Author, Book, Genre } from '../entities';
                import { IGenericRepository } from './generic-repository.abstract';

                export abstract class IDataServices {
                abstract authors: IGenericRepository<Author>;

                abstract books: IGenericRepository<Book>;

                abstract genres: IGenericRepository<Genre>;
                }
                -
                • 这里有三个 Repository 分别对应不同的实体。
                • IGenericRepository 中定义的函数是每个 Repository 公开的通用存储函数。

                以上这些就是对业务中的实体的 Repository 抽象,这样就隔离开了业务和存储逻辑,例如使用 MongoDB,可以实现一个 MongoGenericRepository:

                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                12
                13
                14
                15
                16
                17
                18
                19
                20
                21
                22
                23
                24
                25
                26
                27
                28
                import { Model } from 'mongoose';
                import { IGenericRepository } from '../../../core';

                export class MongoGenericRepository<T> implements IGenericRepository<T> {
                private _repository: Model<T>;
                private _populateOnFind: string[];

                constructor(repository: Model<T>, populateOnFind: string[] = []) {
                this._repository = repository;
                this._populateOnFind = populateOnFind;
                }

                getAll(): Promise<T[]> {
                return this._repository.find().populate(this._populateOnFind).exec();
                }

                get(id: any): Promise<T> {
                return this._repository.findById(id).populate(this._populateOnFind).exec();
                }

                create(item: T): Promise<T> {
                return this._repository.create(item);
                }

                update(id: string, item: T) {
                return this._repository.findByIdAndUpdate(id, item);
                }
                }
                -

                然后实现一个 MongoDataServices:

                1
                2
                3
                4
                5
                6
                7
                8
                9
                10
                11
                12
                13
                14
                15
                16
                17
                18
                19
                20
                21
                22
                23
                24
                25
                26
                27
                28
                29
                30
                31
                32
                33
                34
                35
                36
                37
                38
                39
                40
                import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
                import { InjectModel } from '@nestjs/mongoose';
                import { Model } from 'mongoose';
                import { IDataServices } from '../../../core';
                import { MongoGenericRepository } from './mongo-generic-repository';
                import {
                Author,
                AuthorDocument,
                Book,
                BookDocument,
                Genre,
                GenreDocument,
                } from './model';

                @Injectable()
                export class MongoDataServices
                implements IDataServices, OnApplicationBootstrap
                {
                authors: MongoGenericRepository<Author>;
                books: MongoGenericRepository<Book>;
                genres: MongoGenericRepository<Genre>;

                constructor(
                @InjectModel(Author.name)
                private AuthorRepository: Model<AuthorDocument>,
                @InjectModel(Book.name)
                private BookRepository: Model<BookDocument>,
                @InjectModel(Genre.name)
                private GenreRepository: Model<GenreDocument>,
                ) {}

                onApplicationBootstrap() {
                this.authors = new MongoGenericRepository<Author>(this.AuthorRepository);
                this.books = new MongoGenericRepository<Book>(this.BookRepository, [
                'author',
                'genre',
                ]);
                this.genres = new MongoGenericRepository<Genre>(this.GenreRepository);
                }
                }
                -

                Sunday, March 30, 2025 7:58 PM:

                感觉以上全错,不应该过度抽象 Repository 的,就让它分散在各个模块中,还可以参考 https://github.com/Papooch/nestjs-cls 实现不会抽象泄漏的事务性 Repository 方法。

                -- cgit v1.2.3