领域驱动设计-代码目录规范说明

转载自https://github.com/Vincedream/ddd-fe-demo

目录结构

├── common
│ ├── components // 公用组件
│ ├── constants // 全局变量
│ │ ├── goods
│ │ │ └── index.js
│ │ ├── …
│ ├── data-source // 数据接口层
│ │ ├── goods
│ │ │ ├── requestApis.js
│ │ │ └── translators.js
│ │ ├── …
│ ├── domains // 领域层
│ │ ├── goods-domain
│ │ │ ├── entities // 实体
│ │ │ │ └── goods.js
│ │ │ └── goodsService.js // 领域Service服务
│ │ ├── …
│ └── util // 公用函数
│ └── http.js
└── page // 页面视图层
├── index
│ ├── App.js
│ ├── components
│ │ ├── GoodsItem.js
│ │ ├── GoodsItem.scss
│ │ ├── Nav.js
│ │ └── Nav.scss
│ ├── index.js
│ └── services // 该页面需要用到的Service
│ └── index.js
├── …

数据接口层 data-source文件夹

  • requestApi:数据请求层,负责 http 请求,是项目中唯一与后端服务进行交流的一层。(存放服务接口,相当于API文件
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))
    })
}

分层作用:在这一层中集结了 interest 领域下所有的接口函数,避免了数据接口分散到各个页面,统一存放更易管理,解决了接口调用不统一问题。

  • translator:数据清洗层,这层负责将后端返回的数据“清洗”,改造成更直观地字段(key)、更方便使用的数据(value)。(对返回的数据做处理
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
    }
}

分层作用: 在这一层对接口字段、内容经过二次加工,避免了后端定义字段不规范、混乱对前端的影响,含义清晰、规范的字段在视图层使用时更具有表现力,解决了接口字段不可控性问题。

数据接口层是整个项目的根基,提供了结构清晰、定义规范、可直接使用的数据。

领域层 domain文件夹

领域层是整个项目的核心层,它掌管了所有领域下的行为与定义,它是整个项目中最能体现业务知识的一层。

  • 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
    }
    // 获取活动时间范围
    getLotteryTimeScope() {
        return `${dayjs(this.startDate).format("M月D日")} - ${dayjs(this.endDate).format("M月D日")}`
    }

    // 获取活动类型描述
    getLotteryType() {
        return this.type && lotteryTypeMap[this.type].title
    }
}

export default Lottery

在前端中,我们把它定义为一个 class 类,构造函数中初始化实体的属性,在类中定义了实体的方法,属性和方法的返回值主要是用于视图层中的直接展示,同一个实体的逻辑确保只在实体类中编写,在不同视图下可复用,解决了判断逻辑重复的问题。

  • service:领域服务层,这一层中定义了领域的行为,供视图层直接调用。(关于领域的服务调用
import {
    getLotteryDetail,
    getPrizeList,
    playLottery,
    savePrizeAddress
} from '@data-source/lottery/requestApis';

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


class LotteryService {
    /**
     * 获取本次抽奖活动详情
     * @param {string} id 活动id
     */
    static getLotteryDetail(id) {
        return getLotteryDetail(id).then(lottery => new Lottery(lottery))
    }

    /**
     * 获取本次抽奖活动的奖品列表
     * @param {string} id 抽奖活动id
     */
    static getPrizeList(id) {
        return getPrizeList(id).then(list => {
            return list.map(item => new Prize(item));
        })
    }

    /**
     * 进行抽奖
     * @param {string} id 抽奖活动id
     */
    static playLottery(id) {
        return playLottery(id).then(result => {
            const { recordId, prize } = result;
            return {
                recordId,
                prize: new Prize(prize)
            }
        })
    }

    /**
     * 填写中奖的收货地址信息
     * @param {Object} param0 中奖记录id以及地址信息
     */
    static savePrizeAddress({ recordId, name, phoneNumber, address }) {
        const data = {
            recordId,
            name,
            phoneNumber,
            address
        }
        return savePrizeAddress(data)
    }
}

export default LotteryService

分层作用: 我们可以看到,Service 层连接了 entity 层与 data-source 层,接收后端返回的数据将其转换成具有属性与方法的 entity 实体类,供视图层直接进行展示。不仅如此,Service 层还定义了该领域下的所有行为,比如填写收货地址。领域服务层涵盖了整个业务领域的行为,直观地体现了业务需求。解决了忽略业务整体的问题。

视图层 page文件夹

视图层也就是我们书写交互逻辑、样式的一层,可以使用纯 HTML 或者框架(React、Vue),这一层只需要调用了领域的服务,将返回值直接体现在视图层中,无需编写条件判断、数据筛选、数据转换等与视图展示无关的逻辑代码,这些“糙活”都在其他层中以已经完成,所以视图层是非常“薄”的一层,只需关注视图的展示与交互,整个 HTML 结构非常直观清晰。

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();
  }
  // 获取用户信息
  getUserInfo = () => {
    UserService.getUserDetail().then(user => {
      this.setState({
        user
      })
    });
  }
  // 获取用户积分
  getUserPonitCount = () => {
    InterestService.getUserPointCount().then(count => {
      this.setState({
        pointCount: count
      })
    })
  }
  render() {
    const { pointCount, user } = this.state;
    return (
      <div className="user-page">
        <h3>个人中心</h3>
        <div className="user">
          <div className="info">
            <div>{user.type === SIGN_USER_TYPE ? `尊敬的${user.getUserTypeTitle()}` : null}{user.name}</div>
            <div>绑定手机号: {user.phoneNumber}</div>
            <div>绑定email: {user.email}</div>
          </div>
          <div className="avatar">
            <img className={`${user.isVip ? 'vip' : ''}`} src={user.avatarUrl} alt=""/>
            { user.isNeedRemindUserVipLack() && user.isVip
              ? <div>会员还有{user.getVipRemainDays()}</div>
              : ''
            }
          </div>
        </div>

        <div className="lottery-tips">
          <div>剩余积分:{pointCount}</div>
          <a href="/interest.html">前往积分权益中心 ></a>
        </div>
      </div>
    );
  }
}

export default App;

分层作用: 将 Service 中返回的数据直接使用,视图层中只编写交互与样式,不管是 HTML 纯粹的结构还是代码可读性,新的设计都更讨人喜欢。除了视图层与前端框架有关,其他层可独立应用于任何框架的,分层的结构解决了视图层过厚问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值