Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Understanding the Frontend DDD Framework Remesh

Tech May 13 1

What is DDD

Domain-Driven Design (DDD) focuses on organizing software architecture around business domains. The "domain" represents a specific business area described through collaboration between various stakeholders including product managers, system architects, and dveelopers. Domain experts use a shared language called the "ubiquitous language" to communicate effectively, allowing all team members to discuss problems using common terminology.

MVC vs DDD

Traditional backend MVC architecture often uses an "anemic model" where behavior and state are separated into different objects - typically POJOs (Plain Old Java Objects) and services. This approach reduces cognitive load for simple business logic and provides high efficiency for straightforward operations. However, this separation violates object-oriented principles by splitting an object's state and behavior, leading to procedural development patterns.

As business complexity increases, service logic becomes difficult to manage, potentially creating ripple effects where changes in one area impact the entire system.

DDD introduces the "rich model" concept that avoids these issues. Instead of separate POJOs and services, DDD uses domain entities with high cohesion. Domain state can only be modified through domain actions, preventing external direct modifications and containing business risks within the domain boundaries.

Advantages of DDD

  • Business level: Universal language based on domain knowledge enables rapid delivery and minimal collaboration overhead
  • Architecture level: Facilitates structural organization and resource focus
  • Code level: Manages complexity effectively

Disadvantages of DDD

Primarily tactical challenges:

  • High cognitive burden and significant costs for decomposing existing business logic
  • Requires skilled teams and lacks tactical best practices
  • Lower efficiency for simple business scenarios due to lack of out-of-the-box frameworks

Frontend Considerations

DDD has gained traction in backend development, with resources like Microsoft's "Tackle Business Complexity in a Microservice with DDD and CQRS Patterns". The strategic benefits of DDD also apply to frontend development.

Consider DDD when:

  • Complex business domains exist (such as products, orders, fulfillment)
  • Code complexity grows proportionally with business complexity
  • Business logic reuse across multiple platforms is needed

DDD Example Implementation

Consider a product with several states: creation, editing, publishing, approval, and retraction. Traditional implementation might look like this:

interface ProductData {
  name: string;
  description: string;
}

class ProductEntity {
  private draftStatus: boolean;
  private approvalStatus: boolean;
  private publishedStatus: boolean;
  private productInfo: ProductData;

  constructor(data: ProductData) {
    this.draftStatus = true;
    this.approvalStatus = false;
    this.publishedStatus = false;
    this.productInfo = data;
  }

  updateProduct(data: ProductData) {
    if (!this.draftStatus) {
      throw new Error('Only draft products can be edited');
    }
    this.productInfo = data;
  }

  submitForApproval() {
    if (!this.draftStatus) {
      throw new Error('Only draft products can be submitted');
    }
    this.approvalStatus = true;
    this.draftStatus = false;
  }

  processApproval(success: boolean) {
    if (!this.approvalStatus) {
      throw new Error('Only pending products can be approved');
    }
    
    if (!success) {
      this.draftStatus = true;
    }
    this.publishedStatus = success;
    this.approvalStatus = false;
  }

  withdraw() {
    this.approvalStatus = false;
    this.draftStatus = true;
  }
}

The DDD approach transforms this by creating separate entities for each state:

// Draft product state
class DraftProduct {
  private content: ProductData;
  
  constructor(data: ProductData) {
    this.content = data;
  }

  modify(data: ProductData) {
    this.content = data;
  }

  requestPublication(): PendingProduct {
    return new PendingProduct(this.content);
  }
}
// Pending approval product state
class PendingProduct {
  private content: ProductData;
  
  constructor(data: ProductData) {
    this.content = data;
  }

  handleReview(approved: boolean): ProductState {
    if (approved) {
      return new PublishedProduct(this.content);
    } else {
      return new DraftProduct(this.content);
    }
  }

  cancelRequest(): DraftProduct {
    return new DraftProduct(this.content);
  }
}
// Published product state
class PublishedProduct {
  private content: ProductData;
  
  constructor(data: ProductData) {
    this.content = data;
  }

  retrieveInfo(): ProductData {
    return this.content;
  }
}

This transformation changes "one entity with multiple states" into "multiple entities with single states", focusing code on business logic for each specific state.

Remesh Framework

Remesh implements DDD concepts in frontend applications using the CQRS pattern, allowing developers to focus solely on business logic while the framework handles other concerns.

CQRS Pattern

Command Query Responsibility Segregation separates read and write operations. Commands modify entity data (create, delete, update), while queries retrieve data without modifying it.

CQRS provides significant performance benefits but introduces challenges like ensuring data synchronization and managing dual models.

Core Concepts

Domain: Business logic containers similar to components

  • State: Domain state management
  • Query: Data retrieval from states
  • Command: State modification operations
  • Event: Domain-related events
  • Effect: Side effects for query/command execution

Source Code Analysis

Remesh utilizes RxJS for event distribution and data flow, abstracting data operations and leveraging RxJS capabilities for updates.

// Domain definition
export const ProductDomain = Remesh.domain({
  name: 'ProductDomain',
  impl: (domain) => {
    // Current product state
    const CurrentProductState = domain.state({
      name: 'CurrentProductState',
      default: {
        type: 'DraftProduct',
        data: null
      }
    });

    // Product query
    const ProductQuery = domain.query({
      name: 'ProductQuery',
      impl: ({ get }) => {
        return get(CurrentProductState());
      }
    });

    // Update command
    const UpdateCommand = domain.command({
      name: 'UpdateCommand',
      impl: (_, newData: any) => {
        const current = newData;
        return CurrentProductState().new({
          type: 'DraftProduct',
          data: current
        });
      }
    });

    // Submit command
    const SubmitCommand = domain.command({
      name: 'SubmitCommand',
      impl: ({ get }) => {
        const currentState = get(CurrentProductState());
        return CurrentProductState().new({
          ...currentState,
          type: 'PendingProduct'
        });
      }
    });

    return {
      query: { ProductQuery },
      command: { UpdateCommand, SubmitCommand }
    };
  }
});

The framwork creates domain storage through context initialization, enabling chainable operations and standardized domain construction.

const initializeDomainStorage = (domainAction) => {
  const domainContext = {
    state: (options) => RemeshState(options),
    query: (options) => RemeshQuery(options),
    event: (options) => RemeshEvent(options),
    command: (options) => RemeshCommand(options),
    effect: (effectHandler) => {
      if (!currentStorage.activated) {
        currentStorage.effects.push(effectHandler);
      }
    }
  };

  const domainImplementation = domainAction.Domain.impl(domainContext, domainAction.args);
  
  const currentStorage = {
    id: generateId(),
    Domain: domainAction.Domain,
    get domain() { return domainImplementation; },
    args: domainAction.args,
    context: domainContext,
    action: domainAction,
    effects: [],
    activated: false
  };

  return currentStorage;
};

Framework integration varies by platform - React uses Context API while Vue employs Provide/Inject mechanisms.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.