”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 清洁架构:遥不可及的理想

清洁架构:遥不可及的理想

发布于2024-11-04
浏览:560

Чистая Архитектура: Недостижимый Идеал

Начало пути

В горах Тибета, в уединенном монастыре, жил молодой Ученик, стремящийся постичь глубины программирования и достичь гармонии в своём коде. Он мечтал создать приложение, которое отражало бы принципы Чистой Архитектуры. Однажды он решил обратиться к мудрому Мастеру за советом.

Ученик подошёл к Мастеру и спросил:

Ученик: "О, мудрый Мастер, я создал приложение для управления покупками. Моя архитектура чиста?"

Мастер: "Покажи мне своё творение, и мы вместе узнаем истину."
Ученик продемонстрировал свой код, где база данных и сценарий использования были объединены.

Код Ученика:

// 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(
    `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: 'рис', cost: 2 });
})();

Мастер, после изучения кода, задумчиво произнес:

Мастер: "Твой код подобен реке, где смешаны чистые и мутные воды. Бизнес-логика и детали переплетены. Чтобы достичь истинной чистоты архитектуры, раздели их, как небо и землю."

Первые шаги к чистой архитектуре

Поняв наставление, Ученик решил разделить код на уровни, выделяя базу данных и сценарий использования в отдельные модули. Он также ввёл интерфейсы, чтобы следовать принципу инверсии зависимостей, который является краеугольным камнем Чистой Архитектуры. Теперь addPurchaseIfCan будет зависеть от интерфейса, а не от конкретной реализации репозитория.

// 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: 'рис', 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 





// 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(
      `SELECT SUM(cost) as totalCost FROM purchases WHERE title = ?`,
      [title]
    );
    const totalCost = row?.totalCost || 0;
    return totalCost;
  }
}

Ученик вернулся к Мастеру и спросил:

Ученик: "Я разделил свой код на уровни, выделив базу данных и сценарий использования в отдельные модули, и использовал интерфейсы для репозитория. Моя архитектура стала чище?"

Мастер, глядя на код, ответил:

Мастер: "Ты сделал шаг вперёд, но вычисление totalCost всё ещё происходит в инфраструктурном слое. Однако totalCost относится больше к бизнес-логике твоего сценария использования. Перенеси это вычисление внутрь сценария использования, чтобы отделить бизнес-правила от деталей хранения данных."

Осознание разделения

Ученик осознал, что totalCost должен быть частью бизнес-логики. Он изменил код, чтобы получать список покупок и вычислять totalCost в сценарии использования.

// useCases/IPurchaseRepository.ts
export interface IPurchaseRepository {
  add(purchase: Purchase): Promise;
  getPurchasesByTitle(title: string): Promise;
}
...
// 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,
    }));
  }
}
// useCases/addPurchaseIfCan.ts
import { IPurchaseRepository, Purchase } from './IPurchaseRepository';

export async function addPurchaseIfTotalCostLessThanLimit(
  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 



Ученик снова подошёл к Мастеру:

Ученик: "Я перенёс вычисление totalCost в сценарий использования и отделил бизнес-логику от инфраструктуры. Моя архитектура стала чище?"

Мастер, с теплотой в голосе, сказал:

Мастер: "Ты сделал значительный прогресс, но арифметические операции могут приводить к неточностям. При работе с десятичными числами обычные операции JavaScript могут быть ненадёжными."

Встреча с деталями реализации

Ученик понял, что работа с числами в JavaScript может вызывать ошибки из-за особенностей представления чисел с плавающей точкой. Он обновил код, используя decimal.js для точных вычислений.

// 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 = totalCost.plus(purchase.cost);
  }

  const newTotalCost = totalCost.plus(cost);

  if (newTotalCost.greaterThanOrEqualTo(limit)) {
    console.log(`Общая стоимость превышает ${limit}.`);
  } else {
    await purchaseRepository.add(purchaseData);
    console.log('Покупка успешно добавлена.');
  }
}

Ученик вернулся к Мастеру:

Ученик: "Я скорректировал арифметические операции с помощью decimal.js, чтобы избежать неточностей. Моя архитектура стала чище?"

Мастер ответил:

Мастер: "Ты проделал хорошую работу, но твой сценарий использования всё ещё содержит детали реализации. Прямая зависимость от decimal.js привязывает бизнес-логику к конкретной библиотеке. Если ты захочешь изменить библиотеку в будущем, тебе придётся менять бизнес-логику."

Инверсия зависимостей

Понимая проблему, Ученик решил абстрагировать арифметические операции, используя инверсию зависимостей, чтобы бизнес-логика не зависела от конкретной реализации.

// useCases/calculator.ts
export abstract class Calculator {
  abstract add(a: string, b: string): string;
  abstract greaterThanOrEqual(a: string, b: string): boolean;
}
// decimalCalculator.ts
import Decimal from 'decimal.js';
import { Calculator } from 'useCases/calculator';

export class DecimalCalculator extends Calculator {
  add(a: string, b: string): string {
    return new Decimal(a).plus(new Decimal(b)).toString();
  }

  greaterThanOrEqual(a: string, b: string): boolean {
    return new Decimal(a).greaterThanOrEqualTo(new Decimal(b));
  }
}
// addPurchaseIfCan.ts
import { IPurchaseRepository, Purchase } from './IPurchaseRepository';
import { Calculator } from 'useCases/calculator';

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 = '0';
    for (const purchase of purchases) {
      totalCost = this.calculator.add(totalCost, purchase.cost.toString());
    }

    const newTotalCost = this.calculator.add(totalCost, cost.toString());

    if (this.calculator.greaterThanOrEqual(newTotalCost, this.limit)) {
      console.log(`Общая стоимость превышает ${this.limit}.`);
    } else {
      await this.purchaseRepository.add({
        id,
        title,
        cost: parseFloat(cost.toString()),
      });
      console.log('Покупка успешно добавлена.');
    }
  }
}
// app.ts
import { initializeDatabase } from './db/init';
import { PurchaseRepository } from './db/purchaseRepository';
import { AddPurchaseIfCan } from './AddPurchaseIfCan';
import { DecimalCalculator } from './decimalCalculator';

(async () => {
  const db = await initializeDatabase();
  const purchaseRepository = new PurchaseRepository(db);
  const calculator = new DecimalCalculator();
  const limit = 99999;
  const addPurchaseUseCase = new AddPurchaseIfCan(
    purchaseRepository,
    calculator,
    limit
  );

  await addPurchaseUseCase.execute({ id: 3, title: 'рис', cost: 2 });
})();

Ученик снова обратился к Мастеру:

Ученик: "Я абстрагировал арифметические операции с помощью инверсии зависимостей. Теперь моя архитектура чиста?"

Мастер ответил:

Мастер: "Ты сделал значительный прогресс. Но помни, что твои сценарии использования всё ещё зависят от деталей языка программирования. Ты используешь Javascript и Typescript, но если эти технологии перестанут быть актуальными, то тебе прийдется полностью все переписать на другой язык."

Принятие и понимание

Ученик в недоумении задумался, а потом спросил:

Ученик: "Мастер, как же мне достичь идеальной чистоты архитектуры, если мои сценарии использования всё ещё зависят от языка программирования?"

Мастер, улыбаясь, ответил:

Мастер: "Как птица не может отделиться от неба, так и архитектура не может быть полностью независимой от среды. Полная независимость невозможна, но стремление к ней обогащает твою архитектуру. Цель Чистой Архитектуры — создать систему, где изменения могут быть внесены с минимальными усилиями, и где бизнес-логика отделена от деталей реализации. Понимание этого баланса — ключ к истинной мудрости."

Ученик почувствовал просветление и сказал:

Ученик: "Благодарю тебя, Мастер. Теперь я понимаю, что совершенство не в абсолютной изоляции, а в гармоничном разделении ответственности."

Мастер: "Иди с миром, Ученик. Твой путь только начинается, но ты уже нашёл направление."

Эпилог

Спустя некоторое время, Ученик заметил, что его приложение стало работать медленнее. Он был озадачен: почему программа, которая раньше работала быстро, теперь еле справляется со своей задачей?

Оказалось, что это вовсе не из-за того, что исходный код увеличился в 3 раза, а из-за того, что вычисление totalCost выполняется не в базе данных. Приложение тратило много ресурсов на пересылку больших объёмов данных из базы данных в приложение и на их обработку. Если бы расчёт происходил непосредственно в базе данных, не требовалось бы передавать тысячи строк между слоями, что значительно ускорило бы процесс.

Ученик хотел обсудить это с Мастером, но тот куда-то пропал, и вопрос остался без ответа.

Увидев пустой монастырь, Ученик достал новую книгу и сказал: "Похоже, мой путь к просветлению привёл меня к новому испытанию — оптимизации производительности."

版本声明 本文转载于:https://dev.to/simprl/chistaia-arkhitiektura-niedostizhimyi-idieal-35bj?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 如何在Java中执行命令提示命令,包括目录更改,包括目录更改?
    如何在Java中执行命令提示命令,包括目录更改,包括目录更改?
    在java 通过Java通过Java运行命令命令可能很具有挑战性。尽管您可能会找到打开命令提示符的代码段,但他们通常缺乏更改目录并执行其他命令的能力。 solution:使用Java使用Java,使用processBuilder。这种方法允许您:启动一个过程,然后将其标准错误重定向到其标准输出。...
    编程 发布于2025-04-06
  • 如何使用Python有效地以相反顺序读取大型文件?
    如何使用Python有效地以相反顺序读取大型文件?
    在python 中,如果您使用一个大文件,并且需要从最后一行读取其内容,则在第一行到第一行,Python的内置功能可能不合适。这是解决此任务的有效解决方案:反向行读取器生成器 == ord('\ n'): 缓冲区=缓冲区[:-1] ...
    编程 发布于2025-04-06
  • 如何从Google API中检索最新的jQuery库?
    如何从Google API中检索最新的jQuery库?
    从Google APIS 问题中提供的jQuery URL是版本1.2.6。对于检索最新版本,以前有一种使用特定版本编号的替代方法,它是使用以下语法:获取最新版本:未压缩)While these legacy URLs still remain in use, it is recommended ...
    编程 发布于2025-04-06
  • 为什么Microsoft Visual C ++无法正确实现两台模板的实例?
    为什么Microsoft Visual C ++无法正确实现两台模板的实例?
    The Mystery of "Broken" Two-Phase Template Instantiation in Microsoft Visual C Problem Statement:Users commonly express concerns that Micro...
    编程 发布于2025-04-06
  • Python读取CSV文件UnicodeDecodeError终极解决方法
    Python读取CSV文件UnicodeDecodeError终极解决方法
    在试图使用已内置的CSV模块读取Python中时,CSV文件中的Unicode Decode Decode Decode Decode decode Error读取,您可能会遇到错误的错误:无法解码字节 在位置2-3中:截断\ uxxxxxxxx逃脱当CSV文件包含特殊字符或Unicode的路径逃...
    编程 发布于2025-04-06
  • 如何同步迭代并从PHP中的两个等级阵列打印值?
    如何同步迭代并从PHP中的两个等级阵列打印值?
    同步的迭代和打印值来自相同大小的两个数组使用两个数组相等大小的selectbox时,一个包含country代码的数组,另一个包含乡村代码,另一个包含其相应名称的数组,可能会因不当提供了exply for for for the uncore for the forsion for for ytry...
    编程 发布于2025-04-06
  • 如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    如何使用Java.net.urlConnection和Multipart/form-data编码使用其他参数上传文件?
    使用http request 上传文件上传到http server,同时也提交其他参数,java.net.net.urlconnection and Multipart/form-data Encoding是普遍的。 Here's a breakdown of the process:Mu...
    编程 发布于2025-04-06
  • 为什么不使用CSS`content'属性显示图像?
    为什么不使用CSS`content'属性显示图像?
    在Firefox extemers属性为某些图像很大,&& && && &&华倍华倍[华氏华倍华氏度]很少见,却是某些浏览属性很少,尤其是特定于Firefox的某些浏览器未能在使用内容属性引用时未能显示图像的情况。这可以在提供的CSS类中看到:。googlepic { 内容:url(&#...
    编程 发布于2025-04-06
  • 如何使用Regex在PHP中有效地提取括号内的文本
    如何使用Regex在PHP中有效地提取括号内的文本
    php:在括号内提取文本在处理括号内的文本时,找到最有效的解决方案是必不可少的。一种方法是利用PHP的字符串操作函数,如下所示: 作为替代 $ text ='忽略除此之外的一切(text)'; preg_match('#((。 &&& [Regex使用模式来搜索特...
    编程 发布于2025-04-06
  • 您可以使用CSS在Chrome和Firefox中染色控制台输出吗?
    您可以使用CSS在Chrome和Firefox中染色控制台输出吗?
    在javascript console 中显示颜色是可以使用chrome的控制台显示彩色文本,例如红色的redors,for for for for错误消息?回答是的,可以使用CSS将颜色添加到Chrome和Firefox中的控制台显示的消息(版本31或更高版本)中。要实现这一目标,请使用以下模...
    编程 发布于2025-04-06
  • 为什么使用Firefox后退按钮时JavaScript执行停止?
    为什么使用Firefox后退按钮时JavaScript执行停止?
    导航历史记录问题:JavaScript使用Firefox Back Back 此行为是由浏览器缓存JavaScript资源引起的。要解决此问题并确保在后续页面访问中执行脚本,Firefox用户应设置一个空功能。 警报'); }; alert('inline Alert'...
    编程 发布于2025-04-06
  • HTML格式标签
    HTML格式标签
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    编程 发布于2025-04-06
  • 找到最大计数时,如何解决mySQL中的“组函数\”错误的“无效使用”?
    找到最大计数时,如何解决mySQL中的“组函数\”错误的“无效使用”?
    如何在mySQL中使用mySql 检索最大计数,您可能会遇到一个问题,您可能会在尝试使用以下命令:理解错误正确找到由名称列分组的值的最大计数,请使用以下修改后的查询: 计数(*)为c 来自EMP1 按名称组 c desc订购 限制1 查询说明 select语句提取名称列和每个名称...
    编程 发布于2025-04-06
  • 大批
    大批
    [2 数组是对象,因此它们在JS中也具有方法。 切片(开始):在新数组中提取部分数组,而无需突变原始数组。 令ARR = ['a','b','c','d','e']; // USECASE:提取直到索引作...
    编程 发布于2025-04-06
  • 为什么尽管有效代码,为什么在PHP中捕获输入?
    为什么尽管有效代码,为什么在PHP中捕获输入?
    在php ;?>" method="post">The intention is to capture the input from the text box and display it when the submit button is clicked.但是,输出...
    编程 发布于2025-04-06

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3