API 응답 정규화

Express.js은 노드에서 API를 구축하기 위한 goto 프레임워크였습니다. 내가 일반적으로 직면하는 문제 중 하나는 충분한 자유가 있으면 각 API 응답이 자체 형태를 취하는 경향이 있다는 것입니다. 이것은 각 원격 호출이 각 응답을 소비하는 방법에 대해 생각해야 할 때 이상한 코드 냄새를 생성합니다.

Note: All examples are using express v4 and body-parser.



const todos = [{ ... }, { ... }]; // an array of todos

router.get('/todos', function(req, res, next){
    res.status(200);
    res.json({
        msg: 'So many things to do',
        todos: todos,
    });
});

router.post('/todos', function(req, res, next){
    const todo = {
        title: req.body.title,
        description: req.body.description,
        completed: false,
        id: uuid(), // generate a random id,
    };
    try {
        todos.push(todo); // imagine this might fail, todo is a remote db :)
    } catch (e) {
        return next(e);
    }
    res.status(201);
    res.json({
        message: 'Todo created',
        data: todo,
    });
});

위에서 각 엔드포인트에는 매우 유사한 구조가 주어진 응답에 대한 고유한 구조가 있음을 볼 수 있습니다. 둘 다 메시지와 데이터 세트를 다시 보내지만 다른 키를 사용합니다. 이 문제는 믹스에 오류를 던지기 시작할 때 더욱 분명해집니다.

API 응답 정규화



객체를 반환하는 함수를 만들어 이 문제를 해결할 수 있습니다. 단순화를 위해 이 개체에는 4개의 키 값 쌍이 있습니다.
  • 데이터 - 객체에 대한 기본 데이터는 모든 유형일 수 있습니다
  • .
  • 상태 - 요청이 성공했으며 1은 정상, 0은 불량
  • 오류 - 처리 중에 생성된 오류 배열입니다
  • .
  • 메시지 - 일어난 일에 대한 사용자 친화적인 메시지

  • function apiResponse(data = {}, status = 1, errors = [], message = '') {
        return {
            data,
            status,
            errors,
            message,
        };
    }
    

    좋은 시작이지만 동료 개발자는 매개변수의 순서를 생각해야 합니다. 개체를 매개변수로 받아들이고 필요한 키를 분해하여 문제를 해결해 보겠습니다.

    function apiResponse({ data = {}, status = 1, errors = [], message = '' }) {
        return {
            data,
            status,
            errors,
            message,
        };
    }
    

    이 솔루션이 작동하는 동안 실수로부터 우리를 보호하지는 않습니다. 초기화 후 개체 구조의 무결성이 위험합니다. 더 많은 제어권을 얻을 수 있도록 apiResponse를 클래스로 바꾸겠습니다.

    class ApiResponse {
        constructor({ data = {}, status = 1, errors = [], message = '' }) {
            this._data = data;
            this._status = status;
            this._errors = errors;
            this._message = message;
        }
    }
    

    내부적으로 res.json()는 페이로드에서 JSON.stringify()를 호출하여 인코딩합니다. stringify의 멋진 측면 영향 중 하나는 개체에 값이 함수인 toJSON 속성이 있는 경우 해당 함수가 호출되어 개체가 직렬화되는 방식을 정의한다는 것입니다. 즉, JSON 문자열에 표시할 키를 선택할 수 있습니다.

    class ApiResponse {
        constructor({ data = {}, status = 1, errors = [], message = '' }) {
            this._data = data;
            this._status = status;
            this._errors = errors;
            this._message = message;
        }
        toJSON() {
            return {
                data: this._data,
                status: this._status,
                errors: this._errors,
                message: this._message,
            };
        }
    }
    

    안타깝게도 자바스크립트 클래스에는 개인 키가 없습니다. 우리가 가진 가장 가까운 것은 Symbols 입니다. 그것들을 사용하여 키를 "비공개"로 만들 수 있습니다.

    const apiResponse = (payload = {}) => {
    
        const DataSymbol = Symbol('data');
        const StatusSymbol = Symbol('status');
        const ErrorsSymbol = Symbol('errors');
        const MessageSymbol = Symbol('message');
    
        class ApiResponse {
            constructor({ data = {}, status = 1, errors = [], message = '' }) {
                this[DataSymbol] = data;
                this[StatusSymbol] = status;
                this[ErrorsSymbol] = errors;
                this[MessageSymbol] = message;
            }
            toJSON() {
                return {
                    data: this[DataSymbol],
                    status: this[StatusSymbol],
                    errors: this[ErrorsSymbol],
                    message: this[MessageSymbol],
                }
            }
        }
    
        return new ApiResponse(payload);
    
    }
    

    Javascript에도 유형이 없지만 getterssetters 가 있습니다. 그것들을 사용하여 할당에 대한 유형 검사를 수행할 수 있습니다. 이것이 코드의 최종 진화입니다.

    const apiResponse = (payload = {}) => {
    
        const DataSymbol = Symbol('data');
        const StatusSymbol = Symbol('status');
        const ErrorsSymbol = Symbol('errors');
        const MessageSymbol = Symbol('message');
    
        class ApiResponse {
            constructor({ data = {}, status = 1, errors = [], message = '' }) {
                this.data = data;
                this.status = status;
                this.errors = errors;
                this.message = message;
            }
    
            get data() {
              return this[DataSymbol];
            }
    
            set data(data) {
              if (typeof data === 'undefined')
                  throw new Error('Data must be defined');
              this[DataSymbol] = data;
            }
    
            get status() {
              return this[StatusSymbol];
            }
    
            set status(status) {
              if (isNaN(status) || (status !== 0 && status !== 1))
                throw new Error('Status must be a number, 1 is OK, 0 is BAD');
              this[StatusSymbol] = status;
            }
    
            get errors() {
              return this[ErrorsSymbol];
            }
    
            set errors(errors) {
              if (!Array.isArray(errors))
                throw new Error('Errors must be an array');
              this[ErrorsSymbol] = errors;
            }
    
            get message() {
              return this[MessageSymbol];
            }
    
            set message(message) {
              if (typeof message !== 'string')
                throw new Error('Message must be a string');
              this[MessageSymbol] = message;
            }
    
            toJSON() {
                return {
                    data: this.data,
                    status: this.status,
                    errors: this.errors.map(e => e.stack ? e.stack : e),
                    message: this.message,
                }
            }
        }
    
        return new ApiResponse(payload);
    
    }
    

    게터와 세터는 초기화 후 응답 객체를 안전하게 변경할 수 있는 기능도 제공합니다. 이제 새로운 apiResponse 기능 🎉을 사용하여 재미있는 부분이 나옵니다!

    const todos = [{ ... }, { ... }]; // an array of todos
    
    router.get('/todos', function(req, res, next){
        res.status(200);
        res.json(apiResponse({
            data: todos,
            message: 'You have a lot todo!',
        }));
    });
    

    GET/todos에서 예상되는 응답

    {
       "data": [{ ... }, { ... }],
       "message": "You have a lot todo!",
       "errors": [],
       "status": 1,
    }
    

    일단 그게 전부 야. 이것은 나의 첫 번째 게시물이며 귀하의 의견을 듣고 싶습니다. 이것이 누군가에게 도움이 되기를 바랍니다. 행복한 코딩!

    좋은 웹페이지 즐겨찾기