清洁架构:遥不可及的理想——给开发者的寓言

清洁架构:遥不可及的理想——给开发者的寓言

在西藏宁静的山区高处,一座古老寺院安静的大厅里,住着一位年轻的学徒。他致力于追求和谐——不仅在他自己内部,而且在他的编程技巧中。他的目标是创建一个完美的应用程序,一个能够体现清洁架构深刻原理的应用程序,就像山间溪流的清澈一样。但他意识到这条道路的艰辛,于是向一位可敬的明师寻求智慧。

徒弟谦卑地走近师父并问道:

—“噢,明智的师父,我已经构建了一个应用程序来管理购买。我的建筑干净纯粹吗?”

师父耐心地观察弟子,回答道:

—“向我展示你所创造的东西,我们将一起辨别真相。”

学徒展示了他的作品,其中数据库逻辑和用例流程交织在一起——业务逻辑与技术框架紧密地交织在一起,就像一张错综复杂的网中的线。

// app.ts
import sqlite3 from 'sqlite3';
import { open, database } from 'sqlite';

interface purchase {
    id: number;
    title: string;
    cost: number;
}

async function initializedatabase(): promise<database> {
    const db = await open({
        filename: ':memory:',
        driver: sqlite3.database,
    });

    await db.exec(`
    create table purchases (
      id integer primary key,
      title text,
      cost real
    )
  `);

    return db;
}

async function addpurchaseifcan(db: database, purchase: purchase): promise<void> {
    const { id, title, cost } = purchase;
    const row = await db.get(
        `select sum(cost) as totalcost from purchases where title = ?`,
        [title]
    );
    const totalcost = row?.totalcost || 0;
    const newtotalcost = totalcost + cost;

    if (newtotalcost  {
    const db = await initializedatabase();
    await addpurchaseifcan(db, { id: 3, title: 'rice', cost: 2 });
})();

</void></database>

大师思考代码后说道:

——“你的代码就像一条河流,目的的清水与实现的泥浆混合在一起。业务和技术问题本应分开进行,但它们已合而为一。为了在你的建筑中实现真正的纯粹,你必须将它们分开,就像天空与大地分开一样。”

道路上的第一步

听从师父的话,徒弟开始着手重构他的代码。他开始分离各层,在数据库和业务逻辑流之间划出明显的界限。他还引入了接口,使他的代码与依赖倒置原则保持一致,这是清洁架构的神圣教义之一。现在,他的应用不再依赖于具体的实现,而是依赖于思想的抽象。

// app.ts

import { initializedatabase } from './db/init';
import { purchaserepository } from './db/purchaserepository';
import { addpurchaseifcan } from './usecases/addpurchaseifcan';

(async () => {
  const db = await initializedatabase();
  const purchaserepository = new purchaserepository(db);

  await addpurchaseifcan(purchaserepository, { id: 3, title: 'rice', cost: 2 });
})();
// usecases/addpurchaseifcan.ts

import { ipurchaserepository, purchase } from './ipurchaserepository';

export async function addpurchaseifcan(
  purchaserepository: ipurchaserepository,
  purchase: purchase
): promise<void> {
  const { id, title, cost } = purchase;

  const totalcost = await purchaserepository.gettotalcostbytitle(title);
  const newtotalcost = totalcost + cost;

  if (newtotalcost 





// usecases/ipurchaserepository.ts

export interface ipurchaserepository {
  add(purchase: purchase): promise<purchase>;
  gettotalcostbytitle(title: string): promise<number>;
}

export interface purchase {
  id: number;
  title: string;
  cost: number;
}
</number></purchase>
// db/init.ts

import sqlite3 from 'sqlite3';
import { open, database } from 'sqlite';

export async function initializedatabase(): promise<database> {
  const db = await open({
    filename: ':memory:',
    driver: sqlite3.database,
  });

  await db.exec(`
    create table purchases (
      id integer primary key,
      title text,
      cost real
    )
  `);

  return db;
}
</database>
// db/purchaserepository.ts

import { database } from 'sqlite';
import { ipurchaserepository, purchase } from 'usecases/ipurchaserepository';

export class purchaserepository implements ipurchaserepository {
  private db: database;

  constructor(db: database) {
    this.db = db;
  }

  async add(purchase: purchase): promise<purchase> {
    const { id, title, cost } = purchase;
    await this.db.run(
      `insert into purchases (id, title, cost) values (?, ?, ?)`,
      [id, title, cost]
    );
    return purchase;
  }

  async gettotalcostbytitle(title: string): promise<number> {
    const row = await this.db.get(
      `select sum(cost) as totalcost from purchases where title = ?`,
      [title]
    );
    const totalcost = row?.totalcost || 0;
    return totalcost;
  }
}
</number></purchase>

徒弟回到师傅并问道:

——“我已经为我的存储库分离了各层并使用了接口。我的建筑现在干净了吗?”

大师再次检查代码,回复:

——“你们已经向前迈出了一步,但总成本的计算仍然停留在基础设施上,它不属于那里。这不是这种智慧应该存在的地方。总成本的知识属于商业领域,而不是地球上的工具。将其移至用例中,过程的智慧可以保持其纯粹性。”

分离的教训

有了这种洞察力,学徒意识到总成本的计算是业务逻辑的一部分。他再次重构了代码,将逻辑转移到用例中,使业务问题不受技术基础设施的影响。

// usecases/ipurchaserepository.ts

export interface ipurchaserepository {
  add(purchase: purchase): promise<purchase>;
-  gettotalcostbytitle(title: string): promise<number>;
+  getpurchasesbytitle(title: string): promise<purchase>;
}
...
</purchase></number></purchase>
// usecases/addpurchaseifcan.ts

import { ipurchaserepository, purchase } from './ipurchaserepository';

export async function addpurchaseifcan(
  purchaserepository: ipurchaserepository,
  purchasedata: purchase,
  limit: number
): promise<void> {
  const { id, title, cost } = purchasedata;

  const purchases = await purchaserepository.getpurchasesbytitle(title);

  let totalcost = 0;
  for (const purchase of purchases) {
    totalcost += purchase.cost;
  }

  const newtotalcost = totalcost + cost;

  if (newtotalcost >= limit) {
    console.log(`total cost exceeds ${limit}.`);
  } else {
    await purchaserepository.add(purchasedata);
    console.log('purchase added successfully.');
  }
}
</void>
// db/purchaserepository.ts

import { database } from 'sqlite';
import { ipurchaserepository } from './ipurchaserepository';

export class purchaserepository implements ipurchaserepository {

  ...

  async getpurchasesbytitle(title: string): promise<purchase> {
    const rows = await this.db.all<purchase>(
      `select * from purchases where title = ?`,
      [title]
    );
    return rows.map((row) => ({
      id: row.id,
      title: row.title,
      cost: row.cost,
    }));
  }
}
</purchase></purchase>

再次回到师父身边,问道:

—“我已将总成本计算转移到用例中,并将业务逻辑与基础设施分开。我的建筑现在纯净了吗?”

师父带着温柔的微笑回答:

——“你已经取得了很大的进步,但要小心——就像山风带来冬天的寒冷一样,你的计算可能会带来隐藏的错误。 javascript 的算术就像新手的思维一样,在处理大数或小数时可能会不精确。”

与无常的相遇

学徒明白javascript中浮点运算的缺陷可能会导致微妙但危险的错误。他修改了代码,转向更可靠的工具,一个专为精确计算而设计的库,寻求工作的清晰度。

// usecases/addpurchaseifcan.ts
+ import decimal from 'decimal.js';
import { ipurchaserepository, purchase } from './ipurchaserepository';

export async function addpurchaseifcan(
  purchaserepository: ipurchaserepository,
  purchasedata: purchase,
  limit: number
): promise<void> {
  const { id, title, cost } = purchasedata;

  const purchases = await purchaserepository.getpurchasesbytitle(title);

  let totalcost = new decimal(0);
  for (const purchase of purchases) {
-    totalcost += purchase.cost;
+    totalcost = totalcost.plus(purchase.cost);
  }

- const newtotalcost = totalcost + cost;
+ const newtotalcost = totalcost.plus(cost);

- if (newtotalcost >= limit) {
+ if (newtotalcost.greaterthanorequalto(limit)) {
    console.log(`total cost exceeds ${limit}.`);
  } else {
    await purchaserepository.add(purchasedata);
    console.log('purchase added successfully.');
  }
}
</void>

他再次问师父:

—“我已经改进了我的计算,使用了更好的工具来避免错误。我的建筑现在达到纯粹了吗?”

上人目光坚定如山,答道:

——“你做得很好,但你的架构仍然受到束缚。您的业​​务逻辑现在取决于这个新工具decimal.js的详细信息。如果有一天你需要改变这个工具,你的逻辑基础就会动摇。真正的纯洁是摆脱这种束缚。”

依赖倒置的智慧

认识到大师话语的深度,学徒试图将他的代码从这种执着中解放出来。他抽象了算术运算,颠倒了依赖关系,这样他的业务逻辑就不再依赖于任何一种工具。

// usecases/calculator.ts
export abstract class calculator {
  abstract create(a: number): calculator;
  abstract add(b: calculator | number): calculator;
  abstract greaterthanorequal(b: calculator | number): boolean;
}
// usecases/addpurchaseifcan.ts
+ import { calculator } from 'usecases/calculator';
- import decimal from 'decimal.js';
import { ipurchaserepository, purchase } from './ipurchaserepository';
// decimalcalculator.ts

import decimal from 'decimal.js';
import { calculator } from 'usecases/calculator.ts';

export class decimalcalculator extends calculator {
  private value: decimal;

  constructor(value: number | decimal) {
    super();
    this.value = new decimal(value);
  }

  create(a: number): calculator {
    return new decimalcalculator(a);
  }

  add(b: calculator | number): calculator {
    return new decimalcalculator(this.value.plus(b.value));
  }

  greaterthanorequal(b: calculator | number): boolean {
    return this.value.greaterthanorequalto(b.value);
  }
}
// useCases/addPurchaseIfCan.ts

import { Calculator } from 'useCases/calculator';
import { IPurchaseRepository, Purchase } from './IPurchaseRepository';

export class addPurchaseIfCan {
  private purchaseRepository: IPurchaseRepository;
  private calculator: Calculator;
  private limit: string;

  constructor(
    purchaseRepository: IPurchaseRepository,
    calculator: Calculator,
    limit: number
  ) {
    this.purchaseRepository = purchaseRepository;
    this.calculator = calculator;
    this.limit = limit.toString();
  }

  async execute(purchaseData: Purchase): Promise<void> {
    const { id, title, cost } = purchaseData;

    const purchases = await this.purchaseRepository.getPurchasesByTitle(title);

    let totalCost = this.calculator.create(0);
    for (const purchase of purchases) {
      totalCost.add(purchase.cost);
    }

    totalCost = totalCost.add(cost);

    if (totalCost.greaterThanOrEqual(this.limit)) {
      console.log(`Total cost exceeds ${limit}.`);
    } else {
      await this.purchaseRepository.add({
        id,
        title,
        cost: parseFloat(cost.toString()),
      });
      console.log('Purchase added successfully.');
    }
  }
}
</void>

最后一次回到师父身边,他问道:

——“我使用依赖倒置抽象了我的操作,确保我的业务逻辑不再与实现绑定。我的建筑现在真的干净了吗?”

师父开示:

——“这条路上你已经走得很远了。但请记住,即使您努力追求纯度,您的用例仍然取决于编写它们的语言。您现在使用的是 javascripttypescript,但有一天这些工具可能会消失。当那一天到来时,你会用新的语言重建一切吗?”

拥抱不完美

学徒对此感到困惑,问道:

——“大师,如果我的用例总是与编写它们的语言联系在一起,我怎样才能在我的架构中实现完美的整洁?”

师父带着理解的柔和微笑回答:

——“就像鸟儿无法离开天空一样,建筑也不能完全脱离其创作的工具。真正的独立是一个崇高的梦想,但却是遥不可及的。然而,对它的追求会给你的建筑带来和谐。 清洁架构的目标不是摆脱所有依赖性,而是创建一个轻松应对变化的系统,并将商业智慧与地球运作分开。理解这种平衡是获得真正智慧的关键。”

学徒,感受到他内心的理解黎明之光,说道:

——“谢谢师父。现在我发现完美不是孤立的,而是责任和目标的和谐。”

师父从座位上站起来,回答:

——“放心吧,学徒。你的旅程才刚刚开始,但你已经找到了自己的路。”

结语

随着时间的流逝,学徒注意到他的应用程序开始变慢。他很困惑,想知道一个曾经运行得如此顺利的系统现在如何在执行任务时陷入困境。

很明显,问题不在于代码的大小不断增长,而在于总成本计算是在数据库外部进行的。该应用程序花费了大量的精力来传输大量数据,只是为了执行本可以在源头完成的任务。如果计算是在数据库内完成的,则无需在层之间发送数千条记录,并且性能仍将保持强劲。

徒弟想向师父询问此事,但师父已经消失,问题一直没有答案。

望着寂静的寺院,徒弟拿起一本新书,微笑着说道:

—“看来我的启蒙之路给我带来了新的挑战 - 性能优化的艺术。”

以上就是清洁架构:遥不可及的理想——给开发者的寓言的详细内容,更多请关注其它相关文章!