Givepo
ບລັອກ

Clean Architecture ແລະ ຫຼັກການ SOLID: ຂຽນຊອບແວທີ່ບຳລຸງຮັກສາງ່າຍໃນໄລຍະຍາວ

ເປັນຫຍັງໂຄດເບສຈຶ່ງຊ້າລົງເມື່ອໃຫຍ່ຂຶ້ນ, ແລະ Clean Architecture, ກົດການເພິ່ງພາ ແລະ ຫຼັກການ SOLID ຊ່ວຍໃຫ້ຕັກກະທາງທຸລະກິດທົດສອບງ່າຍ ແລະ ແກ້ໄຂງ່າຍໄດ້ແນວໃດ, ພ້ອມຕົວຢ່າງ TypeScript ທີ່ຄົບຖ້ວນ.

ທີມງານ Givepo
ອ່ານ 7 ນາທີ
Illustration of concentric architecture layers with arrows pointing inward to the core, beside a SOLID principles checklistວິສະວະກຳຊອບແວ

ຊອບແວສ່ວນໃຫຍ່ບໍ່ໄດ້ລົ້ມເຫຼວເພາະສ້າງບໍ່ໄດ້. ມັນລົ້ມເຫຼວເພາະຫຼັງຈາກຜ່ານໄປໜຶ່ງ ຫຼື ສອງປີ, ການປ່ຽນແປງແຕ່ລະເທື່ອໃຊ້ເວລາດົນກວ່າເທື່ອກ່ອນ, ການແກ້ໄຂນ້ອຍໆເຮັດໃຫ້ຟີເຈີທີ່ບໍ່ກ່ຽວຂ້ອງພັງ, ແລະ ບໍ່ມີໃຜກ້າແຕະສ່ວນແກນກາງ. Clean Architecture ແລະ ຫຼັກການ SOLID ແມ່ນຊຸດແນວຄິດທີ່ປ້ອງກັນເລື່ອງນີ້ ໂດຍການແຍກສ່ວນຕ່າງໆຂອງລະບົບທີ່ປ່ຽນແປງດ້ວຍເຫດຜົນຕ່າງກັນອອກຈາກກັນ.

ເປັນຫຍັງໂຄດເບສຈຶ່ງເສື່ອມສະພາບ

ການເສື່ອມສະພາບເກືອບທັງໝົດມາຈາກການຜູກຕິດກັນ (coupling): ກົດທາງທຸລະກິດຮູ້ຈັກ HTTP request, ແຖວຂໍ້ມູນໃນຖານຂໍ້ມູນ ແລະ component ຂອງ UI. ເມື່ອຕັກກະການຄິດລາຄາຢູ່ໃນ controller ທີ່ທັງແປງ JSON ແລະ ຂຽນ SQL, ທ່ານຈະທົດສອບການຄິດລາຄາບໍ່ໄດ້ຖ້າບໍ່ມີເວັບເຊີບເວີ ແລະ ຖານຂໍ້ມູນ. ປ່ຽນຖານຂໍ້ມູນບໍ່ໄດ້ໂດຍບໍ່ກະທົບການຄິດລາຄາ, ແລະ ນຳການຄິດລາຄາໄປໃຊ້ຄືນໃນ background job ບໍ່ໄດ້ເລີຍ.

ສະຖາປັດຕະຍະກຳທີ່ດີແມ່ນການຂີດເສັ້ນແບ່ງຂອບເຂດ ເພື່ອໃຫ້ໂຄດທີ່ມີຄຸນຄ່າທີ່ສຸດ ເຊິ່ງກໍຄືກົດທາງທຸລະກິດຂອງທ່ານ ເພິ່ງພາສິ່ງອື່ນໃຫ້ໜ້ອຍທີ່ສຸດ.

ກົດການເພິ່ງພາ (Dependency Rule)

Clean Architecture ຕາມທີ່ Robert C. Martin ອະທິບາຍໄວ້ ຈັດໂຄດເປັນຊັ້ນຊ້ອນກັນເປັນວົງ:

  1. Entities ຢູ່ກາງ: ອອບເຈັກ ແລະ ກົດທາງທຸລະກິດຫຼັກ ເຊັ່ນ: ຄຳສັ່ງຊື້ ແລະ ວິທີຄຳນວນຍອດລວມ.
  2. Use cases ອ້ອມຮອບຊັ້ນທຳອິດ: ຂັ້ນຕອນການເຮັດວຽກສະເພາະຂອງແອັບ ເຊັ່ນ: ການສັ່ງຊື້ ຫຼື ການຄືນເງິນ.
  3. Interface adapters: controller, presenter ແລະ repository ທີ່ແປງຂໍ້ມູນລະຫວ່າງ use case ກັບໂລກພາຍນອກ.
  4. Frameworks ແລະ drivers ຢູ່ຊັ້ນນອກສຸດ: ເວັບເຟຣມເວີກ, ຖານຂໍ້ມູນ, UI ແລະ ບໍລິການພາຍນອກ.

ກົດຂໍ້ດຽວທີ່ເຮັດໃຫ້ທຸກຢ່າງໄດ້ຜົນແມ່ນ: ການເພິ່ງພາໃນ source code ຕ້ອງຊີ້ເຂົ້າທາງໃນເທົ່ານັ້ນ. ຊັ້ນໃນບໍ່ຮູ້ຫຍັງກ່ຽວກັບຊັ້ນນອກເລີຍ. use case ບໍ່ເຄີຍ import client ຂອງຖານຂໍ້ມູນ, ແຕ່ຈະປະກາດ interface ທີ່ອະທິບາຍສິ່ງທີ່ຕົນຕ້ອງການ ເອີ້ນວ່າ port, ແລ້ວໃຫ້ຊັ້ນນອກເປັນຜູ້ implement ເອີ້ນວ່າ adapter. ນີ້ແມ່ນເຫດຜົນທີ່ແນວທາງນີ້ຖືກເອີ້ນອີກຊື່ໜຶ່ງວ່າ ports and adapters ຫຼື hexagonal architecture.

ໂຄງສ້າງໂຟນເດີທີ່ເຮັດໃຫ້ເຫັນຊັ້ນຕ່າງໆໄດ້ຊັດເຈນ ອາດມີລັກສະນະດັ່ງນີ້:

Text
src/
  domain/            entities and value objects; no framework imports
    order.ts
  application/       use cases and the ports they need
    ports.ts
    place-order.ts
  infrastructure/    adapters: database, email, payment provider
    postgres-order-repository.ts
    email-notifier.ts
  interface/         HTTP handlers, CLI commands, UI
    http/orders-controller.ts
  main.ts            composition root: wires adapters into use cases

ຕົວຢ່າງທີ່ຄົບຖ້ວນດ້ວຍ TypeScript

ເລີ່ມຈາກ entity ເຊິ່ງມີແຕ່ກົດທາງທຸລະກິດລ້ວນໆ ແລະ ບໍ່ import ຫຍັງຈາກເຟຣມເວີກເລີຍ:

TypeScript
// domain/order.ts
export type OrderLine = { sku: string; quantity: number; unitPriceCents: number }

export class Order {
  private constructor(
    readonly id: string,
    readonly customerId: string,
    readonly lines: readonly OrderLine[]
  ) {}

  static create(id: string, customerId: string, lines: OrderLine[]): Order {
    if (lines.length === 0) throw new Error('An order needs at least one line')
    if (lines.some((line) => line.quantity <= 0)) throw new Error('Quantities must be positive')
    return new Order(id, customerId, lines)
  }

  get totalCents(): number {
    return this.lines.reduce((sum, line) => sum + line.quantity * line.unitPriceCents, 0)
  }
}

Use case ອະທິບາຍຂັ້ນຕອນການເຮັດວຽກ ແລະ ປະກາດ port ທີ່ຕົນເພິ່ງພາ. ສັງເກດວ່າ interface ເປັນຂອງຊັ້ນແອັບພລິເຄຊັນ ບໍ່ແມ່ນຂອງໂຄດຖານຂໍ້ມູນ:

TypeScript
// application/ports.ts
import type { Order } from '../domain/order'

export interface OrderRepository {
  save(order: Order): Promise<void>
}

export interface Notifier {
  orderPlaced(order: Order): Promise<void>
}

// application/place-order.ts
import { Order, type OrderLine } from '../domain/order'
import type { Notifier, OrderRepository } from './ports'

export class PlaceOrder {
  constructor(
    private readonly orders: OrderRepository,
    private readonly notifier: Notifier
  ) {}

  async execute(input: { customerId: string; lines: OrderLine[] }) {
    const order = Order.create(crypto.randomUUID(), input.customerId, input.lines)
    await this.orders.save(order)
    await this.notifier.orderPlaced(order)
    return { orderId: order.id, totalCents: order.totalCents }
  }
}

Adapter ໃນຊັ້ນນອກ implement port ດ້ວຍເທັກໂນໂລຊີຕົວຈິງ, ແລະ composition root ເຊື່ອມທຸກຢ່າງເຂົ້າກັນຕອນເລີ່ມລະບົບ:

TypeScript
// infrastructure/postgres-order-repository.ts
import type { Pool } from 'pg'
import type { OrderRepository } from '../application/ports'
import type { Order } from '../domain/order'

export class PostgresOrderRepository implements OrderRepository {
  constructor(private readonly db: Pool) {}

  async save(order: Order) {
    await this.db.query('INSERT INTO orders (id, customer_id, total_cents) VALUES ($1, $2, $3)', [order.id, order.customerId, order.totalCents])
  }
}

// main.ts
const placeOrder = new PlaceOrder(new PostgresOrderRepository(pool), new EmailNotifier(mailer))

ຜົນຕອບແທນເຫັນໄດ້ຊັດຕອນທົດສອບ. use case ທົດສອບໄດ້ໃນບໍ່ເທົ່າໃດມິລລິວິນາທີດ້ວຍຂອງປອມງ່າຍໆໃນໜ່ວຍຄວາມຈຳ, ບໍ່ຕ້ອງມີຖານຂໍ້ມູນ, ບໍ່ຕ້ອງມີເມວເຊີບເວີ ແລະ ບໍ່ຕ້ອງໃຊ້ໄລບຣາຣີ mock:

TypeScript
import { expect, test } from 'vitest'
import { PlaceOrder } from './place-order'
import type { Order } from '../domain/order'

test('places an order and notifies the customer', async () => {
  const saved: Order[] = []
  const notified: Order[] = []
  const placeOrder = new PlaceOrder({ save: async (order) => void saved.push(order) }, { orderPlaced: async (order) => void notified.push(order) })

  const result = await placeOrder.execute({
    customerId: 'customer-1',
    lines: [{ sku: 'TS-1042', quantity: 2, unitPriceCents: 1500 }],
  })

  expect(result.totalCents).toBe(3000)
  expect(saved).toHaveLength(1)
  expect(notified).toHaveLength(1)
})

ຫຼັກການ SOLID ເທື່ອລະຂໍ້

SOLID ແມ່ນຫຼັກການອອກແບບແບບ object-oriented ຫ້າຂໍ້ ທີ່ສະໜັບສະໜູນເປົ້າໝາຍດຽວກັນໃນລະດັບ class ແລະ ໂມດູນ.

S: ຫຼັກຄວາມຮັບຜິດຊອບດຽວ (Single Responsibility)

ໂມດູນໜຶ່ງຄວນມີເຫດຜົນໃນການປ່ຽນແປງພຽງຂໍ້ດຽວ, ໝາຍຄວາມວ່າມັນຄວນຕອບສະໜອງຄົນພຽງກຸ່ມດຽວ ຫຼື ເລື່ອງດຽວ. class ໃບແຈ້ງໜີ້ທີ່ທັງຄຳນວນພາສີ, ສ້າງ PDF ແລະ ສົ່ງອີເມວ ຈະຕ້ອງປ່ຽນທຸກເທື່ອທີ່ພະແນກການເງິນ, ພະແນກອອກແບບ ຫຼື ພະແນກ IT ປ່ຽນໃຈ. ຄວນແຍກເປັນຕົວຄຳນວນ, ຕົວສ້າງເອກະສານ ແລະ ຕົວສົ່ງ.

O: ຫຼັກເປີດ/ປິດ (Open/Closed)

ໂຄດຄວນເປີດໃຫ້ຂະຫຍາຍ ແຕ່ປິດບໍ່ໃຫ້ແກ້ໄຂ: ເພີ່ມພຶດຕິກຳດ້ວຍການເພີ່ມໂຄດໃໝ່ ບໍ່ແມ່ນແກ້ໂຄດທີ່ທົດສອບແລ້ວ. ເຕັກນິກທີ່ໃຊ້ກັນທົ່ວໄປແມ່ນ strategy pattern:

TypeScript
interface DiscountRule {
  appliesTo(order: Order): boolean
  amountCents(order: Order): number
}

const rules: DiscountRule[] = [new LoyaltyDiscount(), new BulkOrderDiscount()]

export const discountCents = (order: Order) => rules.filter((rule) => rule.appliesTo(order)).reduce((sum, rule) => sum + rule.amountCents(order), 0)

// A new promotion is a new class in the list, not another branch in a growing if/else

L: ຫຼັກການແທນທີ່ຂອງ Liskov

ທຸກ implementation ຂອງ interface ຕ້ອງໃຊ້ແທນໄດ້ໃນທຸກບ່ອນທີ່ຕ້ອງການ interface ນັ້ນ ໂດຍບໍ່ເຮັດໃຫ້ຜູ້ເອີ້ນແປກໃຈ. ReadOnlyOrderRepository ທີ່ implement OrderRepository ແຕ່ໂຍນຂໍ້ຜິດພາດເມື່ອເອີ້ນ save() ແມ່ນການລະເມີດຫຼັກການນີ້. ຜູ້ເອີ້ນທີ່ເພິ່ງພາສັນຍານັ້ນຈະພັງຕອນເຮັດວຽກ. ຖ້າສິ່ງໃດບໍ່ສາມາດເຮັດຕາມສັນຍາໄດ້ຄົບ, ມັນຕ້ອງໃຊ້ interface ອື່ນທີ່ນ້ອຍກວ່າ.

I: ຫຼັກການແຍກ Interface

client ບໍ່ຄວນຕ້ອງເພິ່ງພາ method ທີ່ຕົນບໍ່ໄດ້ໃຊ້. ເລືອກໃຊ້ interface ນ້ອຍໆຫຼາຍໂຕທີ່ເນັ້ນຊັດເຈນ ເຊັ່ນ OrderReader ແລະ OrderWriter ແທນ interface ໃຫຍ່ໂຕດຽວທີ່ມີຊາວ method. interface ນ້ອຍໆ implement ງ່າຍກວ່າ, ເຮັດຂອງປອມໃນການທົດສອບງ່າຍກວ່າ ແລະ ແກ້ໄຂງ່າຍກວ່າ.

D: ຫຼັກການປີ້ນທິດການເພິ່ງພາ (Dependency Inversion)

ນະໂຍບາຍລະດັບສູງຄວນເພິ່ງພາ abstraction ບໍ່ແມ່ນລາຍລະອຽດທີ່ເປັນຮູບປະທຳ. use case PlaceOrder ຂ້າງເທິງເພິ່ງພາ interface OrderRepository ບໍ່ແມ່ນ PostgreSQL, ຖານຂໍ້ມູນຈຶ່ງປ່ຽນໄດ້ໂດຍທີ່ຕັກກະທາງທຸລະກິດບໍ່ຮັບຮູ້. ຫຼັກການນີ້ແມ່ນເຄື່ອງຈັກຂອງກົດການເພິ່ງພາ.

Design Pattern ທີ່ຄວນຮູ້ຈັກ

  • Repository: ເຊື່ອງວິທີເກັບ entity ໄວ້ຫຼັງ interface ທີ່ໃຊ້ງານຄ້າຍກັບຄອລເລັກຊັນ.
  • Strategy: ສະຫຼັບອັລກໍຣິທຶມທີ່ໃຊ້ແທນກັນໄດ້ ເຊັ່ນ ກົດການຄິດລາຄາ, ຄ່າຂົນສົ່ງ ຫຼື ສ່ວນຫຼຸດ ຢູ່ຫຼັງ interface ດຽວ.
  • Adapter: ຫໍ່ API ຂອງພາກສ່ວນທີສາມດ້ວຍ interface ທີ່ແອັບຂອງທ່ານກຳນົດເອງ.
  • Factory: ລວມການສ້າງ ແລະ ກວດສອບອອບເຈັກທີ່ຊັບຊ້ອນໄວ້ບ່ອນດຽວ ຄືກັບທີ່ Order.create ເຮັດ.
  • Observer ແລະ domain event: ໃຫ້ສ່ວນອື່ນຂອງລະບົບຕອບສະໜອງຕໍ່ "ມີຄຳສັ່ງຊື້ໃໝ່" ໂດຍທີ່ຄຳສັ່ງຊື້ບໍ່ຕ້ອງຮູ້ວ່າໃຜກຳລັງຟັງຢູ່.
  • Decorator: ເພີ່ມການເກັບແຄດ, ການບັນທຶກ log ຫຼື ການລອງໃໝ່ອ້ອມຮອບ implementation ທີ່ມີຢູ່ ໂດຍບໍ່ຕ້ອງແກ້ໄຂມັນ.

ເມື່ອໃດບໍ່ຄວນອອກແບບເກີນຈຳເປັນ

Clean Architecture ເປັນເຄື່ອງມື ບໍ່ແມ່ນສາສະໜາ. ແອັບ CRUD ນ້ອຍໆທີ່ມີຕັກກະທາງທຸລະກິດໜ້ອຍ ບໍ່ຈຳເປັນຕ້ອງມີສີ່ຊັ້ນ ແລະ interface ອີກຫຼາຍສິບໂຕ. ພິທີການເຫຼົ່ານັ້ນຈະມີຕົ້ນທຶນຫຼາຍກວ່າສິ່ງທີ່ຊ່ວຍປະຢັດໄດ້. ໃຊ້ແນວຄິດເຫຼົ່ານີ້ໃຫ້ເໝາະສົມກັບຄວາມຊັບຊ້ອນ ແລະ ອາຍຸການໃຊ້ງານທີ່ຄາດໄວ້ຂອງໂຄດ:

  • ແຍກກົດທາງທຸລະກິດອອກຈາກ controller ແລະ component ຂອງ UI ສະເໝີ ເຖິງແມ່ນໃນໂປຣເຈັກນ້ອຍ. ມັນແທບບໍ່ມີຕົ້ນທຶນ.
  • ນຳ port ແລະ adapter ມາໃຊ້ໃນບ່ອນທີ່ມີເຫດຜົນແທ້: ບໍລິການພາຍນອກທີ່ອາດປ່ຽນ, ຕັກກະທີ່ຕ້ອງທົດສອບໄດ້ໄວ, ຫຼື ມີຫຼາຍຊ່ອງທາງເຂົ້າ ເຊັ່ນ API, job ແລະ CLI.
  • ຢ່າສ້າງ interface ທີ່ມີ implementation ພຽງໂຕດຽວ ແລະ ບໍ່ມີຂອງປອມສຳລັບການທົດສອບ. ໃຫ້ເພີ່ມເມື່ອຄວາມຕ້ອງການທີສອງປາກົດຂຶ້ນ (you aren’t gonna need it ຫຼື YAGNI).
  • ຄ່ອຍໆ refactor ເຂົ້າຫາສະຖາປັດຕະຍະກຳນີ້ເທື່ອລະໂມດູນ ໂດຍເບິ່ງຈາກບ່ອນທີ່ການປ່ຽນແປງສ້າງຄວາມລຳບາກແທ້.

ປະເດັນສຳຄັນ

  • ຄວາມງ່າຍໃນການບຳລຸງຮັກສາມາຈາກການຈັດການການເພິ່ງພາ: ໃຫ້ກົດທາງທຸລະກິດເປັນອິດສະຫຼະຈາກເຟຣມເວີກ ແລະ ໂຄງສ້າງພື້ນຖານ.
  • ການເພິ່ງພາຊີ້ເຂົ້າທາງໃນ. use case ປະກາດ port ແລະ ຊັ້ນນອກເປັນຜູ້ສະໜອງ adapter.
  • SOLID ນຳແນວຄິດດຽວກັນມາໃຊ້ກັບ class ແລະ ໂມດູນ: ຄວາມຮັບຜິດຊອບດຽວ, ຂະຫຍາຍແທນການແກ້ໄຂ, ສັນຍາທີ່ກົງໄປກົງມາ, interface ຂະໜາດນ້ອຍ ແລະ ການເພິ່ງພາ abstraction.
  • ປັບລະດັບຂອງສະຖາປັດຕະຍະກຳໃຫ້ເໝາະສົມກັບຄວາມຊັບຊ້ອນຂອງບັນຫາ.

ສະຫຼຸບ

ໃນທີ່ສຸດແລ້ວ, Clean Architecture ແມ່ນການປົກປ້ອງໂຄດທີ່ມີຄຸນຄ່າທີ່ສຸດຂອງທ່ານ ເຊິ່ງກໍຄືກົດທີ່ເຮັດໃຫ້ທຸລະກິດດຳເນີນໄປໄດ້ ຈາກການປ່ຽນແປງຂອງເຟຣມເວີກ, ຖານຂໍ້ມູນ ແລະ ຊ່ອງທາງການສົ່ງມອບ. ໃຫ້ການເພິ່ງພາຊີ້ເຂົ້າທາງໃນ, ໃຫ້ໂມດູນເນັ້ນຊັດເຈນ ແລະ ທົດສອບຕັກກະທາງທຸລະກິດໄດ້ໂດຍບໍ່ຕ້ອງເພິ່ງໂຄງສ້າງພື້ນຖານ. ແລ້ວໂຄດເບສຂອງທ່ານຈະຍັງແກ້ໄຂໄດ້ໄວໃນປີທີຫ້າ ຄືກັບໃນປີທຳອິດ.

ອ່ານເພີ່ມເຕີມ

  • #Architecture
  • #Clean Architecture
  • #Design Patterns
  • #SOLID
  • #TypeScript
ຂຽນໂດຍ
ທີມງານ Givepo

Givepo ແມ່ນບໍລິສັດໄອທີທີ່ພັດທະນາເວັບໄຊ, ແອັບມືຖື ແລະ ຊອບແວສະເພາະທາງ ເພື່ອຊ່ວຍໃຫ້ທຸລະກິດຂອງທ່ານກ້າວໄປຂ້າງໜ້າ.

ແບ່ງປັນບົດຄວາມນີ້

ປຶກສາຟຣີ 30 ນາທີ

ລົມກັບທີມງານຂອງພວກເຮົາກ່ຽວກັບໄອເດຍ, ຂອບເຂດວຽກ ແລະ ງົບປະມານຂອງທ່ານ.

ນັດໝາຍປຶກສາ

ບົດຄວາມອື່ນໆ

ບລັອກ
Backend ແລະ API
·ອ່ານ 7 ນາທີ

ອະທິບາຍສະຖາປັດຕະຍະກຳ Backend: REST API, Microservices ແລະ ຖານຂໍ້ມູນທີ່ຂະຫຍາຍໄດ້

ວິທີອອກແບບ REST API ທີ່ນັກພັດທະນາມັກໃຊ້, ເມື່ອໃດທີ່ Microservices ຄຸ້ມຄ່າກັບຕົ້ນທຶນ, ວິທີອອກແບບຖານຂໍ້ມູນທີ່ຮັບພາລະໜັກໄດ້, ແລະ ຮູບແບບທີ່ເຮັດໃຫ້ລະບົບ Backend ໄວ ແລະ ໝັ້ນຄົງ.

ເບິ່ງລາຍລະອຽດ
ພັດທະນາເວັບ
·ອ່ານ 6 ນາທີ

ພັດທະນາເວັບສະໄໝໃໝ່ດ້ວຍ Next.js, React ແລະ TypeScript: ແນວທາງປະຕິບັດທີ່ດີທີ່ສຸດ

Server Components, ກົນລະຍຸດການ render, ຂໍ້ມູນທີ່ປອດໄພດ້ວຍ type ແລະ Core Web Vitals. ຄູ່ມືປະຕິບັດຕົວຈິງສຳລັບສ້າງເວັບແອັບທີ່ໄວ ແລະ ບຳລຸງຮັກສາງ່າຍ ດ້ວຍຊຸດເທັກໂນໂລຊີທີ່ທີມສ່ວນໃຫຍ່ເລືອກໃຊ້ໃນທຸກວັນນີ້.

ເບິ່ງລາຍລະອຽດ
AI ແລະ Machine Learning
·ອ່ານ 8 ນາທີ

AI ແລະ Machine Learning ໃນການນຳໃຊ້ຕົວຈິງ: ສ້າງຄຸນຄ່າໃຫ້ທຸລະກິດໄດ້ຢູ່ໃສແດ່

ມອງໃຫ້ຜ່ານກະແສ. ເຂົ້າໃຈວ່າ Machine Learning ແລະ Generative AI ເຮັດວຽກແນວໃດແທ້, ສ້າງຄຸນຄ່າທີ່ວັດແທກໄດ້ໃນວຽກແບບໃດແດ່ໃນທຸກວັນນີ້, ແລະ ຈະເຮັດໂປຣເຈັກ AI ທຳອິດໃຫ້ນຳໃຊ້ໄດ້ແທ້ແນວໃດ ແທນທີ່ຈະຈົບແຄ່ເດໂມ.

ເບິ່ງລາຍລະອຽດ