文章首发于个人博客 https://github.com/mcuking/bl...译者:这篇文章是在 medium 讲解前端架构分层系列的第一篇文章,分层和以前翻译的文章相似,相对普通项目多出来两层,领域层(从业务抽离出来领域实体)和用例层(实现应用业务逻辑)。另外在编程范式上,相对面对对象,做者更倾向于采用函数式,读者可根据项目特色选择适合本身的方式。html
原文连接 https://blog.codeminer42.com/...前端
这篇博客是《可扩展的前端》系列的一部分,你能够看到其余部分:
#2 — Common Patterns 和 #3 — The State Layer。node
关于软件开发的可扩展性这一律念有两个最多见的的意义:代码的性能和可维护性。你能够同时兼顾这两点,可是专一于良好的可维护性会让一件事情变得容易,那就是提高性能且不影响应用的其他部分。更重要的是,前端与后端有一个重要的区别:本地状态。git
在这个系列博客中,咱们将会讨论如何经过实际的通过验证的方法,来开发和维护可扩展的前端应用。咱们大部分的例子将会使用 React 和 Redux,可是咱们会常常与其余的技术栈对比较,来展现你如何达到一样的结果。让咱们开始这个关于架构方面的系列讨论吧,这是你的软件中最重要的部分。github
那么架构究竟是什么?说架构是软件中的最重要的部分彷佛很自觉得是,但请耐新看下去。数据库
架构是使软件的各个部分相互交互以突出必需要作出的最重要的决策,而且推迟次要的决策和实现细节的方式。设计一个软件的架构意味着将实际的应用从支持它的技术中分离开来。你的实际应用不知道数据库、AJAX 请求、或者 GUI;而是由用例和领域模型组成。这些用例和领域模型表明了你的软件所涵盖的概念,请忽略执行用例的角色或数据在哪里存储等。npm
关于架构还有一个重要的事情要说:那就是架构不意味着文件的组织,也不是如何命名你的文件和文件夹。 |
dependency container
。是的,看起来很奇怪,并且扩展性很差,但这并不意味着用例的实现就在该对象内。这些只是委托给用例的方法,这些用例在其余地方定义。应用程序的全部用例一块儿使用一个对象比将它们分布在整个代码库中要好得多,后者会使它们很难找到。有了这个对象,咱们要作的就是将其注入到 actions 中,让每一个 action 决定将触发什么用例,对吗? 若是你使用的是 redux-thunk,则使用 withExtraArgument 方法能够很容易地实现它,该方法容许你将容器中的每一个 thunk 动做做为 getState 以后的第三个参数注入。若是你使用的是 redux-saga,则该方法应该很简单,在该方法中,咱们将容器做为 run 方法的第二个参数进行传递。若是你使用的是 Ember 或 Angular,则内置的依赖项注入机制就足够了。 这样作会使 action 与用例解耦,由于你无需在定义 action 的每一个文件中手动导入用例。并且将 actions 与用例分开进行测试如今变得很是简单:只需注入一个伪造的用例实现便可,该实现的行为彻底符合你想要的方式。你是否想测试若是用例失败,将 dispatch 什么 action?注入一个老是失败的模拟用例,而后测试 action 如何对此作出响应。无需考虑实际用例如何工做。 太好了,咱们将 state 层注入了 view 层,并将 application 层注入了 state 层。其他的呢?咱们如何将依赖项注入用例来构建
dependency container
?这是一个重要的问题,有不少方法能够解决。首先,不要忘记检查你使用的框架是否内置了依赖项注入,例如 Angular 或 Ember。若是确实如此,则你不该该本身构造。若是没有,你能够经过两种方式来作到这一点:手动或在软件包的帮助下。 手动进行操做应该很简单: - 将你的模块定义为类或闭包, - 首先实例化没有依赖性的模块, - 而后再实例化有依赖的的模块,将它们做为参数传递, - 重复上述步骤,直到实例化全部用例为止, - 导出它们。 太抽象了?看一些代码示例: container.js
`
js import api from './infra/api'; // has no dependencies import { validateUser } from './domain/user'; // has no dependencies import makeUserRepository from './infra/user/userRepository'; import makeArticleRepository from './infra/article/articleRepository'; import makeCreateUser from './app/user/createUser'; import makeGetArticle from './app/article/getArticle'; const userRepository = makeUserRepository({ api }); const articleRepository = makeArticleRepository({ api }); const createUser = makeCreateUser({ userRepository, validateUser }); const getArticle = makeGetArticle({ userRepository, articleRepository }); export { createUser, getArticle };
`
createUser.js
`
js export default ({ validateUser, userRepository }) => async userData => { if (!validateUser(userData)) { throw new Error('Invalid user'); } try { const user = await userRepository.add(userData); return user; } catch (error) { throw error; } };
`
userRepository.js
`
js export default ({ api }) => ({ async add(userData) { const user = await api.post('/users', userData); return user; } });
`
你会注意到,重要部分(用例)已在文件末尾实例化,而且是惟一导出的对象,由于它们将被注入到 actions 中。你的其他代码无需了解 repository 的操做方式和工做方式。这并不重要,而只是技术细节。对于用例,repository 是发送 AJAX 请求仍是在 LocalStorage 中保留某些内容都没有关系;用例没有职责须要知道。若是你想在 API 仍在开发中时使用 LocalStorage,而后切换为使用经过网络 API 的调用,只要与 API 交互的代码遵循与 LocalStorage 交互的接口,而无需更改用例。 即便你有数十个 use cases(用例), repositories, services 等,也能够如上所述手动完成注入。若是太麻烦而没法构建全部依赖关系,则能够始终使用依赖注入的库,只要它不会增长耦合。 检验你的 DI(Dependency injection) 库是否足够好的一条经验法则是,检查从手动方法转移到使用库是否只须要操做 container 代码便可。若是不是这样,则说明库太过侵入,你应该选择其余库。若是你确实要使用库,咱们建议你使用
Awilix。它很是简单易用,无需手动操做,只需操做 container 文件便可。这个库的做者撰写了一系列有关如何使用以及为何使用它的很好的文章,
点击查看。 ## 接下来 好的,咱们已经讨论了架构以及如何以一种很好的方式链接各层!在下一篇文章中,咱们将为刚才讨论的层展现一些实际的代码和通用模式,但 state 层除外,它会在单独的文章中介绍。花一些时间来吸取这些概念。当咱们详细介绍这些模式时,它们将很是有用,一切都会变得更加有意义。到时候那里见! ## 推荐阅读连接
NodeJS and Good Practices
Bob Martin — Architecture the Lost Years
Rebecca Wirfs-Brock — Why We Need Architects (and Architecture) on Agile Projects
Domain-Driven Design