0

0

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

花韻仙語

花韻仙語

发布时间:2024-10-15 08:09:01

|

457人浏览过

|

来源于dev.to

转载

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

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

徒弟谦卑地走近师父问道

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

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

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

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

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

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

async function initializedatabase(): promise {
    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 {
    const { id, title, cost } = purchase;
    const row = await db.get<{ totalcost: number }>(
        `select sum(cost) as totalcost from purchases where title = ?`,
        [title]
    );
    const totalcost = row?.totalcost || 0;
    const newtotalcost = totalcost + cost;

    if (newtotalcost < 99999) {
        await db.run(
            `insert into purchases (id, title, cost) values (?, ?, ?)`,
            [id, title, cost]
        );
        console.log('purchase added successfully.');
    } else {
        console.log('total cost exceeds 99999.');
    }
}

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

大师思考代码后说道:

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

道路上的第一步

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

// 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 {
  const { id, title, cost } = purchase;

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

  if (newtotalcost < 99999) {
    await purchaserepository.add(purchase);
    console.log('purchase added successfully.');
  } else {
    console.log('total cost exceeds 99999.');
  }
}
// usecases/ipurchaserepository.ts

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

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

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

export async function initializedatabase(): promise {
  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;
}
// 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 {
    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 {
    const row = await this.db.get<{ totalcost: number }>(
      `select sum(cost) as totalcost from purchases where title = ?`,
      [title]
    );
    const totalcost = row?.totalcost || 0;
    return totalcost;
  }
}

徒弟回到师傅并问道:

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

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

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

分离的教训

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

// usecases/ipurchaserepository.ts

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

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

export async function addpurchaseifcan(
  purchaserepository: ipurchaserepository,
  purchasedata: purchase,
  limit: number
): promise {
  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.');
  }
}
// db/purchaserepository.ts

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

export class purchaserepository implements ipurchaserepository {

  ...

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

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

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

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

——“你已经取得了很大的进步,但要小心——就像山风带来冬天的寒冷一样,你的计算可能会带来隐藏的错误。 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 {
  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.');
  }
}

他再次问师父:

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

Trae
Trae

字节跳动推出的AI编程IDE工具

下载

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

——“你做得很好,但你的架构仍然受到束缚。您的业​​务逻辑现在取决于这个新工具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 {
    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.');
    }
  }
}

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

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

师父开示:

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

拥抱不完美

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

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

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

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

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

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

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

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

结语

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

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

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

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

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

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

558

2023.06.20

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

416

2023.07.04

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

756

2023.07.04

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

479

2023.09.01

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

534

2023.09.04

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

1091

2023.09.04

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

659

2023.09.12

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

554

2023.09.20

c++ 根号
c++ 根号

本专题整合了c++根号相关教程,阅读专题下面的文章了解更多详细内容。

58

2026.01.23

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
TypeScript 教程
TypeScript 教程

共19课时 | 2.4万人学习

TypeScript——十天技能课堂
TypeScript——十天技能课堂

共21课时 | 1.1万人学习

TypeScript-45分钟入门
TypeScript-45分钟入门

共6课时 | 0.5万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号