weixin_39548606
weixin_39548606
2020-12-30 23:25

自己研究的一套微架构,在Angular项目里用,拿出来探讨探讨

目前在写一个大型ERP项目,因为一直用Angular,所以对架构这块也是非常感兴趣的。

你有一个观点是,这套理论很难被开发者应用起来,其实不然,你提出的这种方式确实复杂了一些(个人感觉有点回到了写Java的时代),但让开发大规模用起来也不难,就是用脚本(CLI)生成整套代码。

说出来你可能不信,我现在页面结构,路由,乃至整个项目,都是由一堆脚本来维护的。如果我将来继续从事Angular,这将是我着力要做的事,就是前端自动化运维——把重复的复杂的烦人的工作都交给脚本一键生成/处理。

自研微架构

好了,说回我的设计。说起来也惭愧,本人所在的地区Angular人才极少,所以我也很难与同事探讨一些架构方面的问题,所以闭门造车是免不了的,我的想法也许有问题,但我不怕丢人,说出来我自己反而好受一些。

Store

从 Redux / Vuex 中吸取了一些精华,感觉Angular也不是不能搞这种东西,而且感觉跟Rxjs很像,所以就用Rxjs实现了一套简单的 Store + Status + Mapper 模型:

  • 有一个公共的 Store 池,任何实体都可以从中注入,抽取,观察里面的值。
 ts
export class Store {
    // 池子里都是用BehaviorSubject装起来的值,一个键只能对应一个值
    static pool: {[key: string]: BehaviorSubject<any>} = {};

    // 用来注入键值
    static set(key, value) {}

    // 可以直接获取值,一般用来不需要观察的业务逻辑里
    static get<t any>(key): T {}

    // 用来追踪某一个值
    static track<t any>(key): BehaviorSubject<t> {}

    // 创建一个状态,返回 T 是希望能够保留它的类型,code的时候很是方便
    static createStatus<t any>(prefix: string, obj: T): T {}
}

</t></t></t></t></any>
  • 每个实体都拥有自己的 Status
 ts
export const AppStatus = Store.createStatus('app', // 前缀,因为直接拿key去存,所以需要用前缀做区分
{
    fullscreen: false, // 默认状态
    loading: false ,
    users: [],
});
  • 每个 Status 也可以有自己的 mapper。其实我项目里没有加 Mapper,因为这个 Mapper 可以在 track 的时候写在属性后面,这样就比较灵活,只针对有需求的加 Mapper。(全局的也能加上,容我想想怎么设计)
 ts
const AppUsersMapper = map(item => {});
...
users = Store.track<user>(AppStatus.users).pipe(AppUsersMapper);
</user>

这样就算转起来了,在项目里用着也是相当巴适。

DAO 层

这块是重点,我觉得前后端啊,还是不能完全脱离,必须得有一个东西可以把他们有机得结合起来。

所以我就自研了一个DAO层。

首选说一下组成: - DaoInterfaces - 这个需要第一个说,为什么呢,这里面存放了与后端数据库表完全相同的接口定义。 - 这些定义怎么来的呢?问得好。这些Interface都是我用脚本分析转化后端代码生成出来的。 - 这意味着什么呢?在模型上,我可以完全跟后端保持同步。 - 这也是我为什么不加 Mapper 的原因,因为没有必要,我直接用后端的结构就可以了。 - 后端来的数据,结构都在前端定义好了,如果这个数据是全局性的,直接扔Store里就好了,类型一传,美滋滋。 - 这些 interface 就像原材料,会扔到下面的工厂里进行加工。

  • DaoFactory
    • 这是用来生成Dao对象的工厂。你想,一千个模型,它们与后端交互的方式是相同的(因为我们用的是restfull API),所以只要抽象出一个Dao模型,再套上不同类型的壳,那它不就能适用于所有模型了吗。
 ts
export class Dao<t any> {
    changed = new EventEmitter(); // 每当某些对数据产生影响的行为触发时 会关联触发这个事件

    // 创建对象
    create(data: T) {}

    // 删除对象
    remove(id) {}

    // 更新对象
    update(data: T) {}

    // 获取单个对象
    get(id) {}

    // 获取列表,DaoQuery 是架构无关的查询条件格式
    list(criteria?: DaoQuery) {}
}
</t>

这样一来,一套模型,就可以复刻出千千万万个子模型

  • DaoService
    • 这里面就是存放那千千万万个子模型的地方~
    • 代码类似于下面这个样
 ts
export class DaoService {
  private dao: any = {};

  ...

  // dao start 这里也是脚本自动生成的代码

  get User() {
    this.preInit('User');
    return this.dao.User as Dao<user>;
  }

  ...

  // dao end

  private preInit(name: string) {
    if (!this.dao[name]) this.dao[name] = this.factory.create(name);
  }
}

// 在视图层就可以直接这样调用
class HomePage {
    // 这样写可能会造成接口重复调用,不推荐
    // users$ = this.dao.User.list();
    users$ = Store.track<user>(AppStatus.users);
    users: User[];

    constructor(private dao: DaoService) {}

    ngOnInit() {
        this.dao.User.changed.subscribe(params => {
            this.dao.User.list(params).subscribe(resp => {
                Store.set(AppStatus.users, resp.results);
                // 或者直接赋值
                this.users = resp.results;
            });
        });
        this.dao.User.changed.emit();
    }

    addUser() {
        // 添加用户
        this.dao.User.create({...}).subscribe(resp => {...})
    }
}
</user></user>
  • DaoStatus
    • 全局Dao的状态,可以存一些全局的数据

小结

通篇来看,发挥作用的有两个重要的点 - 前端与后端的约定 > 前端自己再造新的模型(好的后端才能有好的前端) - 重复的工作交给脚本去做,这样你能省掉大量的时间

嗯,目前就这些,非常欢迎大家能一起探讨一下~ 献丑了~

该提问来源于开源项目:phodal/clean-frontend

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答

14条回答

  • weixin_39929961 weixin_39929961 4月前

    实际上关于前后端交互这一块,确实完全是模板化的代码,如果后端是使用类似swagger这种工具的话,完全可以自动生成requestresponse对应的实体信息,以及基于HttpClient的请求层的代码,我们之前在这一块就是基于阿里巴巴的pont工具做了所有请求层代码的自动生成和同步(重写了生成模板,使其与angular风格保持一致)

    像下面截图里面的代码全是依赖于swagger和pont的vscode插件自动生成 image

    点赞 评论 复制链接分享
  • weixin_39707597 weixin_39707597 4月前

    android官方去年提供了一套 Lifecycle + ViewModel + LiveData + Room + WorkManager 现在社区基本使用这套方案了, MVP也在用

    点赞 评论 复制链接分享
  • weixin_39548606 weixin_39548606 4月前

    感觉总想自己搞一套能解决痛点的框架出来,但是越深入越发现这里面的自由度太高了,就像下围棋一样,每次都能发现新的套路。

    点赞 评论 复制链接分享
  • weixin_39707597 weixin_39707597 4月前

    是的,oop思想就是这样,越深入就会发现大局观很重要

    点赞 评论 复制链接分享
  • weixin_39942474 weixin_39942474 4月前

    Haha,

    我挺欣赏这种自己造轮子的做法。只是呢,自己造轮子有一个坑,就是如何让其他/她人能接受这些规则?这也是为什么我们偏向于使用重复代码的原则,大家学习成本比较低。

    能分享一下相关的经验吗?

    点赞 评论 复制链接分享
  • weixin_39548606 weixin_39548606 4月前

    嗯,关于让别人接受这一点,我想说,不管是哪个行业,只要想让大家接受你的观点/产品,都需要用心运营。

    就跟打造一个爆款产品一样,得去宣传,类似于小米那种。且不说能不能让大家接受,起码能让更多的人看到你的观点,而且还得持续性输出,最好能有大咖带带节奏。

    其实为什么说选框架得先看爸爸是谁,爸爸不好的并不一定真的不行,而是公信力不够。所以接不接受,还是得看运营~

    点赞 评论 复制链接分享
  • weixin_39942474 weixin_39942474 4月前

    哈哈,有机会可以试试,你那有相关的 Demo 应用吗?

    点赞 评论 复制链接分享
  • weixin_39548606 weixin_39548606 4月前

    关于我的框架还有很多其他层面的设计,比如插件机制(第三方库分包),异模块实体组件共享(可以在任意模块创建任意组件),http封装,等等。

    我也挺愿意学习一些新的东西,可以一块讨论讨论。

    点赞 评论 复制链接分享
  • weixin_39548606 weixin_39548606 4月前

    Demo得抽空写一下,我也这两天可以把代码提到github上

    点赞 评论 复制链接分享
  • weixin_39548606 weixin_39548606 4月前

    感觉大家好像没有理解你的意思啊,理解的太片面了。

    文章里提到的理念,主要是为了更规范地分流代码,让非视图层的架构能更通用一些。 我觉得这种想法挺好的,尤其是在一个项目中可能会有多种技术栈存在的情况下。 唯一让我觉得不好的地方就是有点繁琐。 架构这种事,个人主观意愿很重,除非你能完全解决他们的问题,否则他们就有理由再造一堆新轮子。

    点赞 评论 复制链接分享
  • weixin_39942474 weixin_39942474 4月前

    哈哈,有没有大部分人理解并不重要。关键是自己的总结和改进。

    PS:作为一个作者,我知道:大部分人是不会认真去文章的。有时候,他/她评论的是文章的标题,而不是文章的内容。

    点赞 评论 复制链接分享
  • weixin_39707597 weixin_39707597 4月前

    做为一个Android开发看到这个项目,曾经趟过到坑历历在目

    点赞 评论 复制链接分享
  • weixin_39942474 weixin_39942474 4月前

    来分享一下你的坑

    点赞 评论 复制链接分享
  • weixin_39707597 weixin_39707597 4月前

    你文中已经提到了,MVP那块, Android这边基本不采用 “Android-Clean-Boilerplate” 里边的设计方法,都是自己实现一套MVP,也如 提到的 RxJs(Android这边是 RxJava)

    点赞 评论 复制链接分享

相关推荐