分享&浅谈项目设计的一些通用关注点

449 阅读6分钟

作为一个在IT界摸爬滚打将近4年的全栈开发猿,从在象牙塔里刚接触编程时的不知其所云,到毕业后为求职弃硬从软,再到现在变秃变强,一路坎坷(脱发),现在终于有时间总结以下自己的开发经验与大家分享交流.如果有理解不到位之处还请各位在评论区留言.

以下排名不分先后

0. 设计需求(UI)

  • 组件化的设计
  • 响应式
  • Accessibility (抱歉不知中文哪个合适)

解决方案

  • mobile first design (优先小设备显示的设计)
  • 保持和UX设计师良好的沟通,设计尽量组件化,多提一些能够减少开发周期的设计反馈
  • 明确前端适配的浏览器以及版本
  • 明确前端适配的显示屏大小
  • 根据以上来确定测试方案
  • Accessibility是一个很难界定的东西,有A,AA,AAA标准,但是基本上要确保的原则包括但不限于:用户只用键盘也能顺利使用你的App,以及在一些屏幕阅读工具例如NV access能够正确的读取内容

1. 安全需求

  • 用户验证与鉴权(Authentication & Authorization)
  • 防范中间人攻击(Man in the middle atack)
  • 防范代码注入,例如JSON注入,SQL注入等(JSON injection & SQL injection)

解决方案

  • 常见的安全框架:Spring security 或者Apache shiro
  • 通过Auth server与reverse proxy来统一解决验证与鉴权.Auth server 处理用户登录以及生成安全令牌而reverse proxy 负责令牌验证和过期处理
  • 使用单向https来提供外部服务,以及双向https用来内部服务间建立安全连接
  • 使用第三方插件来确保JSON 和 SQL中的恶意注入代码失效(大部分框架都有提供SQL安全防护)

2. 日志,监控以及追踪需求

  • 同步日志与异步日志的选择
  • 不同日志框架的选择logback vs log4j2
  • 日志格式Json vs 传统格式
  • 日志处理

解决方案

  • Log4j2 搭配异步日志来达到最小次I/O写入,最大性能输出.并且搭配shutdown hook来确保程序停止运行时,缓存的日志写入磁盘
  • Elastic search (索引)+logstash (处理)+ kibana(展示)+beats (收集)
  • 如果接收到的请求没有UUID,使用filter等生成并添加,然后使用MDC来将这个UUID包括在每一条日志记录中.如果需要调用下游api, 将此UUID添加到请求头当中
  • 通过分析日志来监控应用,以便及时发现,处理异常(ELK)
  • 统一的日志格式:Json易于ELK来处理,但是笔者更喜欢传统格式,代价便是在logstash需要多花些功夫
  • 服务追踪:zipkin对代码的侵入性很低,能够提供直观的api调用链以及在在每个环节的时间消耗.

3. 存储需求

  • 不同的数据类型:结构性数据与非结构性数据,小数据与大数据
  • 数据缓存
  • 有限制的数据(例如最多可储存数量)
  • 读写数据带来的性能瓶颈
  • 数据审计(谁创建,谁更改,什么时候创建,什么时候更改)
  • 不同数据库的选择
  • 数据加密
  • 不同的数据更新频率
  • 一直在增长的数据

解决方案

笔者对于数据库的研究并不多,这里只做了解内的阐述

  • 使用数据库索引
  • 使用缓存Redis 或者hazelcast
  • 读写分离
  • 利用spring自带的auditaware功能来audit所有数据
  • 数据加密(TBD)
  • 对于不经常更新的数据,可放入property文件中
  • 如果数据的数量一直会增长,那么一开始最好设计分页
  • 如果一个Query可能带来m个其他的Query(例如一个Order中有多个产品), 考虑用空间换时间,将m个子类创建一个只读版本并存储在父类中

4. 通信需求

  • 不同通信协议的需求:http, smtp, ftp, websocket, soap, message broker
  • 不同的格式:Json vs. Xml
  • 统一处理输入与输出
  • 调用或者提供不同特性的Api:只能调用一次,可重复调用(幂等与非幂等),有间隔的调用,定时调用
  • re-try失败请求

解决方案

  • 使用interceptor来统一处理请求和响应
  • 尽量简单的通信接口,将逻辑放在service中
  • Utility 来重新发送请求

5. 测试需求

  • 单元测试
  • 集成测试(Mock所依赖的下游服务)
  • 系统集成测试
  • 性能测试
  • 并发测试

解决方案

  • SonarQube,Jacoco等第三方来提供直观的反馈
  • 不单纯追求测试覆盖率,根据项目压力与经验灵活调整
  • 每一个发现的Bug都写一个测试
  • 在CI/CD中执行单元测试以及集成测试(集成测试慢但是有时候是必要的)
  • 单独建立一个项目来周期性的执行系统集成测试,测试结果以及失败测试存储到数据库,每一个测试都有UUID来识别
  • Jmeter是一个非常好用和上手的性能测试工具,记得使用non-GUI模式
  • 在测试中加入一定的并发测试,确保得到正确的返回值

6. 验证需求

  • 表单验证
  • 外部输入信息的验证以及内部逻辑的验证

解决方案

  • Hibernate validation annotation, 笔者本人并不喜欢用,因为太松散了,而且验证的东西多了的话可读性差.个人喜欢专门写一个验证层,根据不同的业务情况来验证.

7. 代码规范需求

  • 高内聚低耦合的代码设计
  • 统一的代码风格以及标准
  • code review

解决方案

  • 使用lint 工具来检查语法
  • 采用团队内共识的代码标准
  • 周期性的code review

8. 共享需求

  • 代码共享

解决方案

  • 采用内部repository来共享代码,例如nexus
  • 用script 来机械的复制代码

9. 错误需求

  • 应用抛出异常(checked vs. unchecked)
  • 调用下游服务失败
  • 异常中事务性的处理
  • 错误boundary
  • fail fast vs fail tolerant

解决方案

  • 抛出checked 如果你希望这个异常调用者处理,抛出unchecked如果这个异常不需要被立刻处理
  • 创建一个错误消息的规范格式,并且每个错误都有root cause,UUID,异常代码
  • 如果有反向代理,生产环境中可以在反向代理中过滤掉错误细节.如果没有反向代理,可以在error class添加jsonIgnore来过滤掉特定域或者单纯的只返回异常代码
  • 500来表示服务器错误,400表示客户端错误
  • 单实例中使用Transactional来正确的回滚
  • 微服务中的事务回滚(TBD)
  • 根据业务来灵活的选择fail fast or fail tolerant

10. Mock 需求,随机数据需求

解决方案

  • 建立一个nodejs项目来生成随机的数据(json),并且可以转化成Sql语句格式. 
  • mock server (目前还没有深入了解)来mock api 调用

11. 文档需求

  • set up文档
  • api 文档

解决方案

通过阅读readme,简单几步即可将应用启动

  • readme 中包含环境信息 os, jvm version, nodejs version 
  • swagger doc, spring-fox来自动生成swagger doc

12. CI/CD需求

  • 全自动
  • 可配置
  • 版本控制,回滚等
  • 快速的构建

解决方案

  • 传统的Jenkins
  • 新型的云CI/CD服务(docker hub)