express app中的一个js模块是否应该无状态?
发布于 9 年前 作者 kolyjjj 4096 次浏览 最后一次编辑是 8 年前 来自 问答

是这样的,我在用express写一个Restful的app,然后其中有一个文件:

'use strict';

import lodash from 'lodash';

const parseActions = actions => {
  return actions.map(e => {
    const temp = e.split(' ');
    return temp.length > 1 ? {
      method: temp[0].trim(),
      urlRegex: temp[1].trim()
    } : {
      urlRegex: temp[0].trim()
    };
  });
};

const authService = {
  excludedAction: [],
  setExcludedAction(actions){
    this.excludedAction = parseActions(actions);
  },
  isExcludedAction(verb, url){
    return lodash.some(this.excludedAction, function(e) {
      const matchUrl = new RegExp('^' + e.urlRegex + '$').test(url);
      return lodash.isEmpty(e.method) ? matchUrl : (e.method.toUpperCase() === verb.toUpperCase() && matchUrl)});
  }
};

export default authService;

这个文件会返回一个叫’authService’的对象,其有一个属性是excludedAction。这个对象在整个app中只有一个,在app运行的时候需要对其赋值:

const excluded = ['/api/login.*', '/api/posts.*', '/api/comments.*', '/api/users.*'];
authService.setExcludedAction(excluded);

而我在测试authService的时候,给excludedAction赋了另一个值,下面是测试:

'use strict';

import should from 'should';
import authService from '../../src/auth/service';

describe('auth service', _ => {
  let originalActions = [];
  before(function(){
    originalActions = authService.excludedAction;
    authService.setExcludedAction([
        'post /api/posts',
        'get /api/posts/.+',
        '/api/comments',
        '/api/users.*'
    ]);
  });

  after(function(){
    authService.excludedAction = originalActions;
  });

  it('should return true given excluded action', function() {
    const result = authService.isExcludedAction('post', '/api/posts');
    result.should.be.exactly(true);
  });

  it('should return true given excluded action which requires regex', function() {
    const result =  authService.isExcludedAction('get', '/api/posts/lkjdslfk123');
    result.should.be.exactly(true) ;
  });

  it('should return false given not excluded url', function() {
    const result = authService.isExcludedAction('get', '/api/posts');
    result.should.be.exactly(false);
  });

  it('should return false given not excluded verb', function() {
    const result = authService.isExcludedAction('delete', '/api/posts');
    result.should.be.exactly(false);
  });

  it('should return true when there is no verb in configuration', function(){
    const result = authService.isExcludedAction('get', '/api/comments');
    result.should.be.exactly(true);
  });

  it('should exclude all users action', function() {
    let result = authService.isExcludedAction('get', '/api/users');
    result.should.be.exactly(true);
    result = authService.isExcludedAction('get', '/api/users/ljsdlkf123');
    result.should.be.exactly(true);
    result = authService.isExcludedAction('put', '/api/users/ljsdlkf123');
    result.should.be.exactly(true);
    result = authService.isExcludedAction('post', '/api/users');
    result.should.be.exactly(true);
    result = authService.isExcludedAction('delete', '/api/users/lksjdfjk89');
    result.should.be.exactly(true);
    result = authService.isExcludedAction('POST', '/api/users/lksjdfjk89/posts');
    result.should.be.exactly(true);
  });
});

然后还有其他的测试,如果去掉测试里面的after方法,那么在单独跑每一个测试文件的时候是过的,但是一起跑的时候就会有问题。后来发现原因是在authService.spec.js这个测试里面改变了excludedAction的值。而其他的测试用的是改变之后的值,这时候其他的测试就挂掉了。

这里的问题是authService在app里面是一个单例,同时它又有状态。这意味着每一个authService的使用者都只能对其状态进行读取,而无法进行修改,否则一旦一个使用者修改了而没有通知另一个使用者,那么程序就会出错。所以,我们在写类似authService的模块的时候,是不是要写成无状态的呢?

谢谢先。

7 回复

无session就无状态

我觉得~不一定~

像 app = express() 然后 app.get(’/’, …) 就是给 app下面的一个默认的 router(app._router ? 记不清了) 来绑定了 / 的 handler, 也是改变了 app 的状态, 测试的时候可以每次去 new 一个 express app. 不一定要公用。

你可以导出 factory method, 然后去实例化 instance. 导出一个 single instance 是比较麻烦。

@i5ting 这里的状态,我觉得是object内部的属性。我从java转过来,像spring里面的service一般都是无状态的,它会被实例化为单例。

@magicdawn 如果是有多个instance的话,那我觉得使用ES6的class比较容易些,导出类,然后new出一个instance。

@kolyjjj whatever , app 是有状态的,所以别怕改变。

@magicdawn 以前在java里面,倾向于无状态的原因是在多线程的情况下,多个线程对同一个状态的改变,搞起来比较复杂。如果没有状态,就比较简单了。不过由于nodejs是单线程的,所以应该不用考虑状态被多个线程改动的情况。

回到顶部