Domain Driven Design - Code catalog specification

Reprinted from https://github.com/Vincedream/ddd-fe-demo

directory structure

├── common
│♪ components / / common components
│♪ constants / / global variables
│ │ ├── goods
│ │ │ └── index.js
│ │ ├── ...
│♪ data source / / data interface layer
│ │ ├── goods
│ │ │ ├── requestApis.js
│ │ │ └── translators.js
│ │ ├── ...
│♪ domains / / domain layer
│ │ ├── goods-domain
│ │ │♪ entities / / entities
│ │ │ │ └── goods.js
│ │ │ └ - goodsService.js / / domain Service
│ │ ├── ...
│ └ -- util / / common function
│ └── http.js
└ - page / / page view layer
├── index
│ ├── App.js
│ ├── components
│ │ ├── GoodsItem.js
│ │ ├── GoodsItem.scss
│ │ ├── Nav.js
│ │ └── Nav.scss
│ ├── index.js
│ └ -- services / / the services required for this page
│ └── index.js
├── ...

Data source folder of data interface layer

  • requestApi: the data request layer, responsible for http requests, is the only layer in the project that communicates with back-end services. (storage service interface)
import axios from '@common/util/http';
src/common/data-source/interest/requestApis.js
import { pointRecordTranslator, pointGiftTranslator } from './translators'

export function getUserPointRecordList() {
    return axios('/interest/pointRecord').then(data => {
        return data.map(item => pointRecordTranslator(item));
    })
}

export function getInterestGiftList() {
    return axios('/interest/gift').then(data => {
        return data.map(item => pointGiftTranslator(item))
    })
}

Layering function: in this layer, all interface functions in the interest field are gathered, which avoids the dispersion of data interfaces to each page, unified storage, easier management, and solves the problem of inconsistent interface calls.

  • translator: data cleaning layer, which is responsible for "cleaning" the data returned from the back end and transforming it into more intuitive field (key) and more user-friendly data (value). (process the returned data)
export function goodsTranslator({
    id,
    goodsName,
    price,
    status,
    activityType,
    desc,
    brand,
    relatedModelId,
    mainPic,
    tag,
    relatedModelImg
}) {
    return {
        id,
        name: goodsName,
        price: (price / 100).toFixed(2),
        status,
        activityType,
        description: desc,
        brandName: brand,
        mainPicUrl: mainPic,
        tags: tag
    }
}

Layering function: after secondary processing of interface fields and contents in this layer, the impact of nonstandard and chaotic back-end definition fields on the front end is avoided. Clear and standardized fields are more expressive when used in the view layer, and the uncontrollability of interface fields is solved.

The data interface layer is the foundation of the whole project and provides data with clear structure, standard definition and direct use.

Domain folder of domain layer

The domain layer is the core layer of the whole project. It takes charge of the behavior and definition in all fields. It is the layer that can best reflect the business knowledge in the whole project.

  • Entity: entity is the carrier of domain services. It defines the attributes and methods of an individual in the business, such as prizes and activities in the lucky draw. These can be abstracted as entities. It is unique in the global domain and cannot have the same entity in other domains. (it's actually a class)
/**
 * Lottery entity
 */
import dayjs from 'dayjs'
import { lotteryTypeMap } from '@constants/lottery'
class Lottery {
    constructor(lottery={}) {
        this.id = lottery.id
        this.name = lottery.name
        this.type = lottery.type
        this.startDate = lottery.startDate
        this.endDate = lottery.endDate
    }
    // Get active time range
    getLotteryTimeScope() {
        return `${dayjs(this.startDate).format("M month D day")} - ${dayjs(this.endDate).format("M month D day")}`
    }

    // Get activity type description
    getLotteryType() {
        return this.type && lotteryTypeMap[this.type].title
    }
}

export default Lottery

In the front end, we define it as a class class, initialize the attributes of the entity in the constructor, and define the methods of the entity in the class. The return values of the attributes and methods are mainly used for direct display in the view layer. The logic of the same entity ensures that it is only written in the entity class and can be reused in different views, which solves the problem of judging logical duplication.

  • Service: domain service layer, which defines the behavior of the domain and can be directly called by the view layer. (about domain service invocation)
import {
    getLotteryDetail,
    getPrizeList,
    playLottery,
    savePrizeAddress
} from '@data-source/lottery/requestApis';

import Prize from './entities/prize';
import Lottery from './entities/lottery';


class LotteryService {
    /**
     * Get the details of this lucky draw
     * @param {string} id Activity id
     */
    static getLotteryDetail(id) {
        return getLotteryDetail(id).then(lottery => new Lottery(lottery))
    }

    /**
     * Get the prize list of this lucky draw
     * @param {string} id Lucky draw id
     */
    static getPrizeList(id) {
        return getPrizeList(id).then(list => {
            return list.map(item => new Prize(item));
        })
    }

    /**
     * Draw
     * @param {string} id Lucky draw id
     */
    static playLottery(id) {
        return playLottery(id).then(result => {
            const { recordId, prize } = result;
            return {
                recordId,
                prize: new Prize(prize)
            }
        })
    }

    /**
     * Fill in the receiving address information of the winning prize
     * @param {Object} param0 Winning record id and address information
     */
    static savePrizeAddress({ recordId, name, phoneNumber, address }) {
        const data = {
            recordId,
            name,
            phoneNumber,
            address
        }
        return savePrizeAddress(data)
    }
}

export default LotteryService

Layering: we can see that the Service layer connects the entity layer and the data source layer, receives the data returned by the back end, and converts it into an entity class with attributes and methods for direct display by the view layer. Moreover, the Service layer also defines all behaviors in this field, such as filling in the receiving address. The domain Service layer covers the behavior of the whole business domain and intuitively reflects the business requirements. It solves the problem of ignoring the whole business.

View layer page folder

The view layer is the layer where we write interactive logic and styles. We can use pure HTML or frameworks (React and Vue). This layer only needs to call domain services and directly reflect the return value in the view layer. There is no need to write logical codes unrelated to view display, such as condition judgment, data filtering and data conversion. These "rough activities" have been completed in other layers, Therefore, the view layer is a very "thin" layer. You only need to pay attention to the display and interaction of the view, and the whole HTML structure is very intuitive and clear.

import React from 'react';

import { UserService, InterestService } from './services';
import User from '@domain/user-domain/entities/user';
import { SIGN_USER_TYPE } from '@constants/user';

import "./App.scss"

class App extends React.Component {
  state = {
    pointCount: null,
    user: new User()
  }
  componentDidMount() {
    this.getUserInfo();
    this.getUserPonitCount();
  }
  // Get user information
  getUserInfo = () => {
    UserService.getUserDetail().then(user => {
      this.setState({
        user
      })
    });
  }
  // Get user points
  getUserPonitCount = () => {
    InterestService.getUserPointCount().then(count => {
      this.setState({
        pointCount: count
      })
    })
  }
  render() {
    const { pointCount, user } = this.state;
    return (
      <div className="user-page">
        <h3>Personal Center</h3>
        <div className="user">
          <div className="info">
            <div>{user.type === SIGN_USER_TYPE ? `honorific ${user.getUserTypeTitle()}: ` : null}{user.name}</div>
            <div>Binding mobile number: {user.phoneNumber}</div>
            <div>binding email:  {user.email}</div>
          </div>
          <div className="avatar">
            <img className={`${user.isVip ? 'vip' : ''}`} src={user.avatarUrl} alt=""/>
            { user.isNeedRemindUserVipLack() && user.isVip
              ? <div>Members and{user.getVipRemainDays()}day</div>
              : ''
            }
          </div>
        </div>

        <div className="lottery-tips">
          <div>Remaining points:{pointCount} branch</div>
          <a href="/interest.html">Go to the points equity Center ></a>
        </div>
      </div>
    );
  }
}

export default App;

Layering: use the data returned from the Service directly, and only write interaction and style in the view layer. Whether it is the pure structure of HTML or the readability of code, the new design is more likable. Except that the view layer is related to the front-end frame, other layers can be applied to any frame independently. The layered structure solves the problem of too thick view layer.

Tags: Javascript Vue.js

Posted on Fri, 26 Nov 2021 05:04:04 -0500 by sdotsen