DEV Community

张一凡
张一凡

Posted on

Domain-Driven Development with easy-model

In e-commerce application development, modules like product management, shopping carts, and order processing often involve complex business logic. easy-model's model-driven architecture can effectively organize this domain knowledge, improving code maintainability. This article demonstrates how to implement domain-driven development in e-commerce using easy-model through real-world examples.

Product Management Model

Products are the core domain in e-commerce. We can create a ProductModel to encapsulate product state and business logic:

import { useModel } from "easy-model";

class ProductModel {
  product = {
    id: "",
    name: "",
    price: 0,
    stock: 0,
    category: ""
  };

  constructor(initialProduct: typeof this.product) {
    this.product = initialProduct;
  }

  updateStock(newStock: number) {
    if (newStock < 0) throw new Error("Stock cannot be negative");
    this.product.stock = newStock;
  }

  isAvailable() {
    return this.product.stock > 0;
  }

  applyDiscount(discountPercent: number) {
    this.product.price *= (1 - discountPercent / 100);
  }
}

function ProductCard({ productId }: { productId: string }) {
  const productModel = useModel(ProductModel, [{
    id: productId,
    name: "iPhone 15",
    price: 5999,
    stock: 10,
    category: "Electronics"
  }]);

  return (
    <div>
      <h3>{productModel.product.name}</h3>
      <p>Price: ${productModel.product.price}</p>
      <p>Stock: {productModel.product.stock}</p>
      <p>Status: {productModel.isAvailable() ? "In Stock" : "Out of Stock"}</p>
      <button onClick={() => productModel.updateStock(productModel.product.stock - 1)}>
        Buy
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This model encapsulates product business rules like stock management and discount application. Domain logic is centralized, avoiding scattered business code in components.

Shopping Cart Shared State

In e-commerce apps, shopping carts need to be shared across multiple components. easy-model's provide mechanism naturally supports instance caching grouped by user:

import { provide, useInstance } from "easy-model";

class CartModel {
  items: Array<{ productId: string; quantity: number }> = [];
  userId: string;

  constructor(userId: string) {
    this.userId = userId;
  }

  addItem(productId: string, quantity: number) {
    const existing = this.items.find(item => item.productId === productId);
    if (existing) {
      existing.quantity += quantity;
    } else {
      this.items.push({ productId, quantity });
    }
  }

  getTotalItems() {
    return this.items.reduce((sum, item) => sum + item.quantity, 0);
  }
}

const CartProvider = provide(CartModel);

function CartIcon() {
  const cart = useInstance(CartProvider("user123"));
  return <div>Cart ({cart.getTotalItems()})</div>;
}

function AddToCartButton({ productId }: { productId: string }) {
  const cart = useInstance(CartProvider("user123"));
  return (
    <button onClick={() => cart.addItem(productId, 1)}>
      Add to Cart
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Through provide, different components share the same cart instance, grouped by user ID, ensuring data consistency.

Order Asynchronous Processing

Order submission involves asynchronous operations. easy-model's @loader.load decorator simplifies loading state management:

import { loader, useLoader, useModel } from "easy-model";

class OrderModel {
  order = { id: "", status: "pending", items: [] };

  constructor(orderId: string) {
    this.order.id = orderId;
  }

  @loader.load(true)
  async submitOrder() {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 2000));
    this.order.status = "confirmed";
  }
}

function OrderForm() {
  const orderModel = useModel(OrderModel, ["order123"]);
  const { isLoading } = useLoader();

  return (
    <div>
      <p>Order Status: {orderModel.order.status}</p>
      <button
        onClick={() => orderModel.submitOrder()}
        disabled={isLoading}
      >
        {isLoading ? "Submitting..." : "Submit Order"}
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

useLoader automatically tracks async method states, no manual loading boolean management needed.

Test-Driven Development

easy-model's model classes are easy to test, ensuring business logic correctness:

import { describe, it, expect } from "vitest";

describe("ProductModel", () => {
  it("should update stock correctly", () => {
    const model = new ProductModel({
      id: "1",
      name: "Test",
      price: 100,
      stock: 5,
      category: "Test",
    });
    model.updateStock(3);
    expect(model.product.stock).toBe(3);
  });

  it("should throw on negative stock", () => {
    const model = new ProductModel({
      id: "1",
      name: "Test",
      price: 100,
      stock: 5,
      category: "Test",
    });
    expect(() => model.updateStock(-1)).toThrow();
  });
});
Enter fullscreen mode Exit fullscreen mode

Unit tests cover business rules, improving code quality.

Conclusion

easy-model shows great strength in e-commerce applications: model encapsulation of domain logic, provide for state sharing, @loader.load for async handling. Combined with Vitest testing, ensures high-quality delivery. Try easy-model for more elegant e-commerce development!

Project repo: GitHub

Top comments (0)