DEV Community

张一凡
张一凡

Posted on

easy-model: The Perfect Fit for Domain-Driven Development

In modern frontend development, Domain-Driven Design (DDD) is gaining traction as a preferred approach. The easy-model framework, with its unique model-driven architecture, offers developers a convenient way to implement DDD. This article highlights how easy-model facilitates domain-driven development, enhances testability, and remains simple to use.

Convenient Domain-Driven Development

At the heart of easy-model is model class encapsulation. Each model class represents a business domain, encapsulating state and logic, avoiding the fragmentation of traditional state management. This design centralizes business logic, making it easy to understand and maintain, perfectly aligning with DDD principles.

import { useModel } from "easy-model";

class UserDomain {
  user = { id: "", name: "", email: "" };
  constructor(initialUser = { id: "", name: "", email: "" }) {
    this.user = initialUser;
  }

  updateUser(newUser: typeof this.user) {
    this.user = { ...this.user, ...newUser };
  }

  validateEmail() {
    return this.user.email.includes("@");
  }
}

function UserComponent() {
  const userDomain = useModel(UserDomain, [{ id: "1", name: "John Doe", email: "john@example.com" }]);

  return (
    <div>
      <h2>{userDomain.user.name}</h2>
      <p>Email valid: {userDomain.validateEmail() ? "Yes" : "No"}</p>
      <button onClick={() => userDomain.updateUser({ name: "Jane Doe" })}>
        Update Name
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

With useModel, components directly create and subscribe to model instances. Model class methods encapsulate business logic, such as validation and updates, ensuring domain knowledge cohesion. Compared to Redux's action/reducer separation, easy-model is closer to OOP thinking.

Superior Testability

easy-model comes with built-in Vitest support, making testing intuitive and efficient. Model classes as pure logic units are ideal for unit testing, without complex mocking.

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

describe("UserDomain", () => {
  it("should validate email correctly", () => {
    const model = new UserDomain({
      id: "1",
      name: "Test",
      email: "test@example.com",
    });
    expect(model.validateEmail()).toBe(true);

    model.updateUser({ email: "invalid" });
    expect(model.validateEmail()).toBe(false);
  });

  it("should update user", () => {
    const model = new UserDomain({
      id: "1",
      name: "Old",
      email: "old@example.com",
    });
    model.updateUser({ name: "New" });
    expect(model.user.name).toBe("New");
  });
});
Enter fullscreen mode Exit fullscreen mode

The framework's instance caching mechanism (provide) allows grouping by parameters, ensuring test isolation. Dependency injection via the inject decorator and container configuration supports mock replacements, improving integration test efficiency.

Simple and Easy-to-Use Experience

easy-model eliminates complex configurations—install and use immediately. Strict TypeScript mode ensures type safety, preventing runtime errors. The API is clean, requiring only a few hooks to get started.

Seamless React integration:

import { useModel, useWatcher } from "easy-model";

function CounterComponent() {
  const counter = useModel(CounterModel, [0, "Counter"]);

  useWatcher(counter, (keys, prev, next) => {
    console.log(`Field ${keys.join(".")} changed from ${prev} to ${next}`);
  });

  return (
    <div>
      <h2>{counter.label}</h2>
      <div>{counter.count}</div>
      <button onClick={() => counter.decrement()}>-</button>
      <button onClick={() => counter.increment()}>+</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Asynchronous operations use the @loader.load decorator, with the useLoader hook for querying states, simplifying loading handling. @offWatch optimizes performance by avoiding unnecessary watching. Cross-component sharing is achieved through provide and useInstance.

Performance and Scalability

easy-model uses Proxy for deep change detection, supporting nested objects and reference relationship changes. The watch function allows listening in non-React environments, while useWatcher handles component side effects. The IoC container supports namespace isolation, facilitating management in large applications.

Compared to Redux/MobX, easy-model reduces boilerplate and boosts development efficiency. The benchmark example shows competitive performance.

Conclusion

easy-model centers on models, perfectly supporting domain-driven development. Its class encapsulation boosts testability, and its clean API ensures ease of use. Whether for small projects or complex applications, it significantly improves development efficiency.

Try easy-model today for a more elegant frontend development experience! Project repo: GitHub

Top comments (0)