-
Notifications
You must be signed in to change notification settings - Fork 0
삽질기
- 원문: https://williamdurand.fr/2014/02/14/please-do-not-patch-like-an-idiot/
- 요약
- PUT은 대체, PATCH는 delta에 대한 수정. (따라서 PUT에서 생략된 필드는 '그대로 유지'하라는 의미가 아니라 '삭제'하라는 의미가 된다.)
- PATCH를 사용할 때는 다음과 같이 변화분에 대한 operation, path(필드명), value를 배열로 넘겨줄 것을 RFC 문서에서 권고하고 있다.
- bad example
PATCH /users/123 { "email": "new.email@example.org", "name": "newName", }
- good example
PATCH /users/123 [ { "op": "replace", "path": "/email", "value": "new.email@example.org" }, { "op": "replace", "path": "/name", "value": "newName" }, ]
- bad example
- 관련 RFC 문서: https://tools.ietf.org/html/rfc6902
- 현실성
- 이제 저 글의 말대로 PATCH를 'idiot'처럼 쓰지 않으려 한다고 생각해보자.
- 먼저 처음 드는 생각은 operation을 enum이나 상수로 정리해야 하냐는 것인데, Node.js 레벨에서 이를 따로 제공해주지 않는 것으로 보인다.
- 저 body를 controller 단에서 파싱해주는 작업이 필요할텐데, 이에 대한 Node.js의 지원이 없는 것 같다. 이걸 또 유틸 함수 만들어서 처리하기도 좀...
- 이 생각을 나만 하는 건가 싶어서 GitHub REST API를 참고해봤다. 나만 그런 생각을 한 게 아닌가보다.
- 결론
- 어플리케이션을 만들 때 로이 필딩 형님이 시키는거는 오지게 안 따라주는 게 국룰.. ㅎ
- DELETE의 body는 명세에서 정의하는 바가 없다. RFC 문서에 따르면 DELETE에 body가 포함된 경우, 구현체에 따라 이 요청을 거부할 수도 있다고 나와있다.
A payload within a DELETE request message has no defined semantics; sending a payload body on a DELETE request might cause some existing implementations to reject the request.
- koa-body에서는 DELETE에 body가 들어가는 것을 허용하지 않는다.
- https://stackoverflow.com/a/41539479/7258660
- DELETE method는 기본적으로 한 번에 하나의 리소스만을 삭제하도록 고안되었다. 하지만, 여러 리소스를 동시에 삭제해야 하는 경우, 삭제 HTTP 요청을 여러번 보내는 건 서버에 부하를 쓸데없이 많이 주는 일이다.
- 여러 리소스를 한 번에 삭제해야 하는 경우, REST에서는
POST {domain}/batch
API를 만들어 사용할 것을 권고한다. batch API는 여러 HTTP 요청을 한번에 body에 담아 보내는 목적으로 쓴다.curl -X POST \ -H "X-Parse-Application-Id: ${APPLICATION_ID}" \ -H "X-Parse-REST-API-Key: ${REST_API_KEY}" \ -H "Content-Type: application/json" \ -d '{ "requests": [ { "method": "POST", "path": "/1/classes/GameScore", "body": { "score": 1337, "playerName": "Sean Plott" } }, { "method": "POST", "path": "/1/classes/GameScore", "body": { "score": 1338, "playerName": "ZeroCool" } } ] }' \ https://api.parse.com/1/batch
- GitHub에서 브랜치를 삭제해봐도 POST가 날아감을 알 수 있다.
- 문제 상황
- 서버에 아무 리소스도 생성하지 않지만, 서버의 기능을 사용해서 input을 가공하여 결과를 받아가기만 하는 경우.
- 사이드이펙 없고 멱등한데 이거 GET 아님???? <- 맞음
- 그럼 문제가 없는 게 아니냐 싶었는데...
- 그런데 짜잔! Axios에서 불가능함.
- 안되는 이유는 Axios가 fetch 또는 XHR을 사용하도록 구현되어 있는데 여기서 GET에 body 넣는 걸 허용해주지 않는다고 함.;;
- 문제 상황
- PUT으로 이미지를 수정하고 싶은데.. 안 올라간다!
- 원인
- REST 규약에서 PUT에는 form 데이터를 허용하지 않음.
- 히스토리
- multipart-form의 태생은
<form>
HTML 태그를 통해 데이터를 보낼 때 사용하는 헤더로부터 비롯되었다. - 2012년에 form이 GET과 POST만 지원하는 것에 대해서 이상하게 여긴 누군가가 PR을 올린 적이 있었다.
- 하지만 Ian Hickson이라는 아조씨가 이상한 소리를 하면서 리젝해버렸다. (DELETE는 별 의미가 없다는 말이 맞다. 하지만 PUT에 payload를 넣으려는 사람이 없을 것이라는 판단은 어디서 나왔는지 잘 모르겠다.)
- 해당 논의는 별도 이슈에서 진행하기로 하고 닫혔다.
- 그리고 놀라울 만큼, 그 누구도 관심을 주지 않았다. (대충 어린 석가모니가 손가락 하나 들고 서있는 짤)
- multipart-form의 태생은
- GET에다가 ObjectId 같은 걸 파라미터로 넘겼다가 255자 넘어가면 어떡하지 하는 걱정에서 찾아봄.
- GET 글자 수가 255자로 제한되어 있다는 건 도시전설이다.
- 브라우저의 구현에 따라 제한되어 있고, 제일 짧은 게 익스플로러9 2053자라고 한다. 그 외의 브라우저들은 최소 몇만자 단위. 출처
- 근데 엔진엑스나 아파치 등의 웹서버에서는 기본설정으로 GET 길이를 8KB로 제한하고 있으며, 이를 넘기면 414 에러를 뱉는다. (실제로 넘기는 걸 본 적이 있음)
- 문제상황
-
Tag
삭제시,Post.tagList
에서도 해당 태그를 하나 삭제해야 한다. - MongoDB에는 제약조건 규칙을 걸 수 있는 기능이 없다.
- 이 제약조건 코드를 Service와 Repository 중 어느 쪽에 구현할 것인가.
-
- 근거
- 애시당초 비즈니스 로직이 아니라 데이터 설계단에서 걸리는 제약조건으로, 이 제약조건을 무시한 채로 데이터 변경이 일어나는 일은 있어서는 안 된다.
- 결론
- Repository에 구현한다.
- logic in getters
- 이런 경우
public class ServerInstance { private int[] instanceNoList; public String[] getInstanceNoList() { return instanceNoList.stream().map(...); // logic in the getter } }
-
https://stackoverflow.com/questions/495864/logic-in-get-part-of-property-good-practice
it would violate the principle of least astonishment
- getter에 기대하는 바와 다른 동작을 포함하기 때문에 좋은 패턴이 아니다.
- 이런 경우
- DTO as class vs interface
- interface DTO에는 로직을 넣을 수 없어서 class로 전환하려 했었으나, DTO에 로직을 넣지 않기로 결정했으므로 그냥 interface를 사용하기로 했다.
- 추가적으로 interface는 타입스크립트 차원에서 타입검사가 이루어지는 장점이 있다.
-
class-transformer
- DTO를 클래스로 전환하려 했을 때 타입검사를 위해 도입하였으나, nested 객체의 경우 타입검사가 되지 않았다.
-
class-validator
를 도입해봤으나 결과는 마찬가지. 방법이 있는지는 잘 모르겠다. - 이 라이브러리의 본래 용도는 TS -> TS로의 데이터 전달시 DTO 필드 검증 목적이 아니다. 외부 모듈에서 들어온 쌩 json 데이터가 우리가 원하는 포맷대로 들어왔는지 검사하는데에 한정적으로 사용된다. 근데 사실 interface로 받으면 될 것 같은데 무슨 의미가 있는지 잘 모르겠다.
- Sharing DTO among projects?
- ex) BE의 ResponseDto를 FE와 공유하여 코드 중복을 줄이고 DRY 원칙을 지켜야 하나?
- BE - FE로만 생각해서 저런 의문이 든건데, 좀 더 일반화해서 MSA 상황을 생각해보자: FE, OpenAPI - BE - API1, API2, API3
- FE, OpenAPI는 BE의 ResponseDto를 공유 받는 게 맞는가? API1 ~ 3은 각각 BE와 ResponseDto를 공유해야 하는가?
- 각 프로젝트는 별도의 시스템이므로, 각자 필요한 내용만을 DTO로 가져가야 한다. FE - BE 상황에서 BE의 ResponseDto에 새로운 필드가 추가됐다 하더라도, FE는 그 필드를 사용할 때에만 필드를 추가해야 한다.
- FE와 BE의 배포주기는 다를 수 있다. BE의 ResponseDto에서 기존 필드가 변경되는 일은 쉽사리 일어나지 않아야 하고, 새로운 필드가 추가되는 경우에는 FE 입장에서는 해당 필드를 사용하는 소스코드가 아무 것도 없는 상태기 때문에 자신의 DTO를 업데이트할 필요가 없다.
- https://softwareengineering.stackexchange.com/a/366237
- 사용되어야만 하는 경우
- 인터페이스가 있고, 여러 구현체가 있는 경우.
- 상황에 맞게 적절한 구현체를 주입해주고, 주입받은 구현체가 무엇인지에 상관없이 인터페이스를 사용할 수 있음.
- 전략 패턴, 추상 팩토리 패턴 등을 사용할 때.
- 레이어 아키텍쳐에서 이게 필요한가?
- Nope.
- 그렇다면 Spring에서는 왜 굳이 여러 구현체가 있지도 않은 케이스에도 DI를 남발하는가?
- 스프링은 IoC를 기본 컨셉으로 잡고 있어서 컨테이너가 빈을 관리하기 때문.
- 일단 빈 등록된 건 다 의존성주입으로 넣어야 함.
- 결론
- 이 블로그에 들어간 DI 다 빼야 함. ㅎㅎ
- js의 decorator로 구현하려다 잘 안되어 콜백 구조로 만들었었는데, 스프링의 AOP도 비슷함.
- 타겟이 인터페이스인 경우: java.reflect의 다이나믹프록시 이용 → js 콜백함수 넘기는거랑 걍 똑같음(InvocationHandler 이용).
- 타겟이 클래스인 경우: CGLib으로 프록시로 감싸진 모양새의 클래스를 직접 새로 생성하여 처리.
- 이거 true 나옴.
- {}.toString이 '[object Object]'를 반환하기 때문 ㅋㅋ
- https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=jdub7138&logNo=221022257248
- require 함수는 가져올 함수를 즉시실행함수로 한 번 감싼다. export 되지 않은 함수나 변수들은 클로저 영역에 들어가서 외부에서 접근이 막힌다.
(function (exports, require, module, __filename, __dirname) { var myModule = function() { ... } module.exports = myModule; } (module.exports, require, module, filename, dirname); return module.exports;
-
delete require.cache[require.resolve('@src/tag/TagRouter')];
를 통해 캐시를 비워줘야 함. - Mocha의 코드들(
describe
,before
,it
등)은 다른 코드들이 모두 실행된 이후에 실행되는 것으로 확인됨. 따라서,before
같은 곳에서 캐시를 비워줘봐야 이미 늦음.- Mocha 코드들의 실행시점에 대해 추가로 조사해봐야 할 것 같음.
- 커스텀 에러에 대한 핸들링을 할 때,
CustomError extends Error
형태로 구현이 된 경우, 발생할 에러가 내가 예측한CustomError
일지, 예상 밖의 그냥Error
일지 알 수 있는 방법이 없음. - 이에 대한 처리를 위해
BlogErrorHandlingUtil
에서는 다음처럼instanceof
를 사용하였음.const blogError: BlogError = error instanceof BlogError ? error as BlogError : new BlogError(BlogErrorCode.UNEXPECTED_ERROR, [], error.toString());
-
instanceof
가 다형성을 해치기 때문에 안티패턴이라는 시각에 동의하기 때문에instanceof
를 걷어낼 방법이 없을지 고민해봄. - 스프링의
ResponseEntityExceptionHandler
코드를 보면 피보탈 행님들이 예외 객체가 어떤 게 들어왔는지 판별하기 위해instanceof
와 분기문을 떡칠해놓은 장관을 볼 수 있음.public final ResponseEntity<Object> handleException(Exception ex, WebRequest request) throws Exception { HttpHeaders headers = new HttpHeaders(); if (ex instanceof HttpRequestMethodNotSupportedException) { HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED; return handleHttpRequestMethodNotSupported((HttpRequestMethodNotSupportedException) ex, headers, status, request); } else if (ex instanceof HttpMediaTypeNotSupportedException) { HttpStatus status = HttpStatus.UNSUPPORTED_MEDIA_TYPE; return handleHttpMediaTypeNotSupported((HttpMediaTypeNotSupportedException) ex, headers, status, request); } else if (ex instanceof HttpMediaTypeNotAcceptableException) { HttpStatus status = HttpStatus.NOT_ACCEPTABLE; return handleHttpMediaTypeNotAcceptable((HttpMediaTypeNotAcceptableException) ex, headers, status, request); } else if (ex instanceof MissingPathVariableException) { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; return handleMissingPathVariable((MissingPathVariableException) ex, headers, status, request); } else if (ex instanceof MissingServletRequestParameterException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleMissingServletRequestParameter((MissingServletRequestParameterException) ex, headers, status, request); } else if (ex instanceof ServletRequestBindingException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleServletRequestBindingException((ServletRequestBindingException) ex, headers, status, request); } else if (ex instanceof ConversionNotSupportedException) { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; return handleConversionNotSupported((ConversionNotSupportedException) ex, headers, status, request); } else if (ex instanceof TypeMismatchException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleTypeMismatch((TypeMismatchException) ex, headers, status, request); } else if (ex instanceof HttpMessageNotReadableException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleHttpMessageNotReadable((HttpMessageNotReadableException) ex, headers, status, request); } else if (ex instanceof HttpMessageNotWritableException) { HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; return handleHttpMessageNotWritable((HttpMessageNotWritableException) ex, headers, status, request); } else if (ex instanceof MethodArgumentNotValidException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleMethodArgumentNotValid((MethodArgumentNotValidException) ex, headers, status, request); } else if (ex instanceof MissingServletRequestPartException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleMissingServletRequestPart((MissingServletRequestPartException) ex, headers, status, request); } else if (ex instanceof BindException) { HttpStatus status = HttpStatus.BAD_REQUEST; return handleBindException((BindException) ex, headers, status, request); } else if (ex instanceof NoHandlerFoundException) { HttpStatus status = HttpStatus.NOT_FOUND; return handleNoHandlerFoundException((NoHandlerFoundException) ex, headers, status, request); } else if (ex instanceof AsyncRequestTimeoutException) { HttpStatus status = HttpStatus.SERVICE_UNAVAILABLE; return handleAsyncRequestTimeoutException((AsyncRequestTimeoutException) ex, headers, status, request); } else { // Unknown exception, typically a wrapper with a common MVC exception as cause // (since @ExceptionHandler type declarations also match first-level causes): // We only deal with top-level MVC exceptions here, so let's rethrow the given // exception for further processing through the HandlerExceptionResolver chain. throw ex; } }
- 피보탈 형님들도 찾지 못한 답을 굳이 내가 찾을 필요는 없을 것 같아 그냥 나도
instanceof
쓰기로 함. -
instanceof
를 걷어낼 수 없는 케이스 판별 기준: 파라미터의 타입이 primitive하거나, 위 예시에서처럼 언어 자체에서 정의한 타입이라 인터페이스를 implement하도록 변경할 수 없는 경우.
- method decorator example
export const testAnnotation = () => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
console.dir(target);
console.dir(propertyKey);
console.dir(descriptor);
const callback: Function = descriptor.value;
descriptor.value = function() { // arrow function을 쓰면 this가 없기 때문에 반드시 그냥 function을 써야 함
return callback.apply(this) + '!!'; // this를 가리키기 위해서는 `apply`를 사용해서 함수를 실행시켜야 함
};
};
class HawiClass {
prefix: string = 'Ay~ '
@testAnnotation()
public sayHawi(): string {
return this.prefix + 'hawi';
}
}
it('annotation test', () => {
const test: string = new HawiClass().sayHawi();
console.log(test);
});
- https://fettblog.eu/typescript-typing-catch-clauses/
- 자바스크립트의 throw는 자바의 throw와는 달리 '아무거나' 던질 수 있다. Error 타입이 아니어도 그냥 다 던짐.
- 에러타입에 따라 다른 에러핸들링을 해야 할 때 자바처럼 여러 개의 catch를 쓰는 건 의미가 없음. 다음처럼 분기문과
instanceof
의 조합을 이용하여 처리하라고 함. (자바와 애초에 문법이 다르기 때문에 안티패턴이 아님.)try { myroutine(); // There's a couple of errors thrown here } catch (e) { if (e instanceof TypeError) { // A TypeError } else if (e instanceof RangeError) { // Handle the RangeError } else if (e instanceof EvalError) { // you guessed it: EvalError } else if (typeof e === "string") { // The error is a string } else if (axios.isAxiosError(e)) { // axios does an error check for us! } else { // everything else logMyErrors(e); } }
- Koa-router는
ctx.body
에 뭔가 내용을 넣어줘야 200이 들어간다.- 문제
-
ctx.body = {something awsome};
의 statement가 없으면 404가 반환된다.
-
- 원인/해결책
-
Koa 공식 홈페이지를 찾아보면 다음과 같은 문장을 확인할 수 있다.
Get response status. By default,
response.status
is set to404
unlike node'sres.statusCode
which defaults to200
. -
koa에서 요청을 처리하는 부분을 살펴보면
statusCode
에 기본적으로 404를 넣는 것을 확인할 수 있다. -
koa에서 body를 수정하는 setter 메소드를 보면 그냥 별 짓 안해도 알아서 200을 집어넣는 것을 볼 수 있다. 따라서
ctx.status = 200;
만으로도 404 반환 이슈는 해결할 수 있다.
-
Koa 공식 홈페이지를 찾아보면 다음과 같은 문장을 확인할 수 있다.
- 문제
- error handling
- 문제
- 모든 컨트롤러에서
try ~ catch
구문을 중복해서 집어넣는 건 잘못된 것 같다.
- 모든 컨트롤러에서
- 원인/해결책
-
Koa Wiki을 보면, 다음과 같은 default error handler가 내장되어 있다고 한다.
app.use(async (ctx, next) => { try { await next(); } catch (err) { // will only respond with JSON ctx.status = err.statusCode || err.status || 500; ctx.body = { message: err.message }; } })
- 즉, 하위 layer에서 에러 발생시 그냥 에러객체에다가
statusCode
또는status
랑message
만 잘 넣어놓고 throw하면 알아서 FE로 에러응답을 던질 수 있다는 뜻이다.
-
Koa Wiki을 보면, 다음과 같은 default error handler가 내장되어 있다고 한다.
- 문제
- middleware
- KOA의 미들웨어는 Spring의 ArgumentResolver 쯤에 대응한다.
- Spring의 filter처럼 모든 요청에 대해 router 인입에 앞서 동작하며, Spring의 interceptor처럼 요청/응답 객체에 접근할 수 있다.
-
tsconfig-paths
패키지를 사용하면tsconfig.json
에 경로의 alias를 넣어줄 수 있다.{ ... "paths": { "@src/*": ["src/*"], "@test/*": ["test/*"] }, ... }
-
ts-node
로 돌릴 때는 이 설정이 잘 들어가는데tsc
는 단일 파일만 돌릴 때tsconfig.json
을 인식하지 못해서 생기는 문제 - 가능한 해결책
-
tsc
가tsconfig.json
을 인식하게 만든다.
- 경로 단위로 돌릴 때는 해당 경로 안에
tsconfig.json
이 들어가서 인식하는데, 단일 파일에는 걔가 빠져서 인식 못하는 것으로 추정됨. - 그럼
tsconfig.json
도 파일 목록에 넣어주면 되지 않을까?
- 1 시도해보고 안되면 시도할 것:
[module-alias](https://www.npmjs.com/package/module-alias)
라는 걸 쓰면 된다더라.
-
- ts-mockito가 나은 점
- ts-mockito가 when/then/given의 BDD 스타일을 제공하기 때문에 가독성이 좋다. (주관적)
- 모듈의 함수를 아예 재정의하는 것이 아니라, 자바의 Mockito처럼 mock 객체를 하나 생성하여 사용하는 형태다. 따라서 각 테스트 파일마다
before
에sinon.restore()
와 같은 작업을 안 해줘도 된다.
- sinon을 써야만 하는 경우
- ts-mockito로 생성한 mock 객체가 DI 방식으로 주입되지 않는 경우에는 사용할 수 없다.
- ts-mockito는 verificator가 부실하다. 얘를 들어, Number.isSafeInteger()로 정수임을 검증하는 로직이 있는 모듈은
anyNumber()
사용해서 모킹하려 할 때, validation에 걸린다. (i.e.crypto.randomInt(min: number, max: number)
)
let mockedFoo:Foo = mock(Foo);
let foo:Foo = instance(mockedFoo); // 빼먹으면 verify 정상동작 안 함!
foo.getBar(3);
foo.getBar(5);
verify(mockedFoo.getBar(3)).called();
verify(mockedFoo.getBar(5)).called();
위 코드에서 instance화 하는 작업이 빠지면 아무리 foo의 함수들 호출해봤자 verify에서 감지를 못한다.
-
storage.dbPath
에 쓰기 권한 들어가야 함.$ chmod g+w primary $ chmod g+w arbiter $ chmod g+w secondary0
-
레플리카셋 초기화 (레플리카셋에 접속이 안될 때) 최초 한 번 초기화가 필요하다.
$ brew services start mongodb-community $ mongo --port 27017 > config = { _id : "blog", members : [ {_id : 0, host : "127.0.0.1:27017"}, {_id : 1, host : "127.0.0.1:27018"}, {_id : 2, host : "127.0.0.1:27020"}, ] } > rs.initiate(config)
config 수정할 때는
rs.initiate(config, {force: true})
이렇게 force 옵션 줘서 덮어써주면 된다.참고: https://knight76.tistory.com/entry/mongodb-replica-set-%EB%A7%8C%EB%93%A4%EA%B8%B0
- FYI
- MongoDB에서는
Collection.insertOne
과Collection.insertMany
를 지원한다. - Mongoose에서는
create
와insertMany
를 지원한다.
- MongoDB에서는
-
create
vsinsertMany
- 둘 다 collection에 새로운 document 추가하는 애들이다.
-
create
는 하나만 추가하고 하나만 리턴한고,insertMany
는 여러개 추가하고 여러개 리턴한다. 그 외의 동작 차이는 없다. -
insertMany
가create
에 비해 성능상 이점이 있다. Mongoose 저자의 말에 따르면insertMany
는 여러 건의 데이터를 저장할 때, hook과 validation들을 건너뛰고 MongoDB driver를 직접 호출하기 때문이라고 한다. 참고
- 결론
-
insertMany
쓰면 됨.
-
- 왜 분산 DB 환경에서는 join이 구현되기 어려운가
- 이 블로그 글에 몽고디비의 전반적인 특징이 잘 설명돼있는데, 보면 분산 시스템을 만들기 위해 조인과 트랜잭션을 포기했다고 한다.
-
https://qr.ae/pN0iin
- NoSQL은 기본적으로 복잡한 SQL 없이 key를 사용해서 get/set하는 것만을 목적으로 하는 데이터베이스다. 여기서 얻은 구조적 단순함으로부터 높은 확장성을 꾀한다.
- 다른 샤드에 데이터를 나눠서 저장한다. 정확한지는 모르겠는데, 데이터의 50%는 A 샤드의 데이터노드에, 나머지는 B 샤드의 데이터노드에 나눠서 저장하는 식. 이렇게 하여, 조회시간을 O(N/샤드수)로 줄일 수 있다.
- 데이터가 여러 샤드에 나뉘어있으므로 인덱스 걸기 힘들어진다. 인덱스의 성능도 샤드 수만큼 줄어들게 되고, 구현도 복잡해진다.
- 데이터가 여러 샤드에 나뉘어있으므로 걍 카테시안 곱 만드는 것부터 쉽지 않다. '브로커'라는 것이 구현되어야 하는데 이는 NoSQL의 'simplicity'와 'easy scalability'에 맞지 않는다.
- 참고로, 트랜잭션은 나중(4.x)에 클러스터 기능이 추가되면서 지원하기 시작했다. 본 프로젝트에서도 잘 쓰고 있다.
- population
- Mongoose에서는 join을 대신해서 population을 제공한다.
- 하지만 얘는 조인이 아니다.
- 그리고 심지어 MongoDB에서 실행되는 것도 아니다.
-
몽구스 문서에 따르면 그냥 find 쿼리 이후에 다시 참조 도큐먼트에다가 N번 실행 때리는 것이라고 한다.
Paths are populated after the query executes and a response is received. A separate query is then executed for each path specified for population. After a response for each query has also been returned, the results are passed to the callback.
- 즉, 이건 성능이 쓰레기다. 그냥 데이터 연결성을 정확히 표현하는 수단 정도로만 쓰고, 다른 collection에 있는 연관 데이터를 읽어와야 하는 상황이라면 직접 생각해서 쿼리를 두 번 호출하거나, 읽어온 데이터에서 Node.js 로직으로 필터링하는 게 좋겠다.
MongoDB ObjectId 극혐...
- find해서 얻어왔을 때
- 다음과 같은
Types.ObjectId
타입의 객체를 얻을 수 있다. ObjectId { _bsontype: "ObjectID", id: { 0: 96, 1: 146, ... 11: 46 } }
- id string 추출하는 법:
ObjectId.toString()
하면 나온다. 따라서 위 객체에다가 바로.toString()
ObjectId 타입 객체를 넣어주면 에러난다.
- 다음과 같은
- create 등 CUD 액션 후에 리턴값을 얻어왔을 때 (UD는 실험적으로 확인된 건 아님)
{ ..., _doc: { _id: { _bsonType: "ObjectID", id: { 0: 96, ... } }, ... } }
- 방법1: ObjectId를 직접 구해서
.toString()
returnObj._doc._id.toString()
- 방법2: 방법1과 동일. 하지만,
_doc
은 생략할 수 있다.returnObj._id.toString()
- 방법3: 다음처럼 호출해도 위와 동일한 결과가 나온다.
returnObj.id
- update해야 할 때
- 내부적으로
new Types.ObjectId()
로 감싸는 것 같다. id string으로 넣어줘야 하고,
- 내부적으로
- insert해야 할 때
- 모르겠음.
- delete해야 할 때
- 모르겠음.
- 그런거 없다. 왜냐하면 MongoDB가 JOIN을 지원하지 않기 때문.
- lazy loading 정도는 고려해볼 수 있지만, 결국 N + 1번 조회해야 함. 상황에 따라 populate를 하느냐 마냐 정도..
- JPA에서 영속성 컨텍스트의 존재 이유: 트랜잭션 내에서 DB 요청 횟수를 최소화하기 위해
- JS 진영의 대표적인 ODM인 Mongoose에서 이를 지원하지는 않는 듯 하다.
Mongoose provides a straight-forward, schema-based solution to model your application data. It includes built-in type casting, validation, query building, business logic hooks and more, out of the box.
- 다음의 목적으로 사용하는 ODM이다.
- 직관적 사용
- 스키마로 관리
- 타입 캐스팅
- 밸리데이션
- 쿼리 빌딩..은 당연한거고;;
- 훅 <- 간단히 공부해봐야겠음
- 그래서 영속성을 유사하게 사용하기 위해 cachegoose라는 것도 있다.
- 참고로, Hibernate를 사용하는 Hibernate OGM 같은 것도 있는데 얘는 된다.
- 다음의 목적으로 사용하는 ODM이다.
- MongoDB에서 영속성의 목적인 속도와 내구성을 대체하는 개념으로 이런 게 있다고 한다.
- 내구성 - journaling: 트랜잭션 수행 전에 일종의 백업본을 메모리에 만들어서 실패시 리트라이하는데 쓴다고 한다.
- 속도 - write semantics: 문서상의 설명은 여기인 듯 한데, 같이 알아야 하는 개념이 있고 꽤나 복잡하다. ReplicaSet의 write concern, write concern에 대해 먼저 알아보도록 하자.. 어쩌면 레포지터리 테스트의 간헐적 실패 원인과 관련이 있을지 모른다.
-
spec: 'test/**/*.ts'
<= 요거 넣는 순간 테스트 로딩속도가 개느려짐. -
**
이 문제. - 나중에 Mocha에다가 이슈나 PR 남겨보자. <= 조금 봤는데 코드가 가이드 없이 따라가기 쉽지 않았음. 그냥 말자...
- 원문: https://blog.travis-ci.com/2021-12-01-pricingenhancements
- 요약:
- credit 차감방식으로 변경됨.
- credit은 매월 1일 자정에 리필됨.
- 따라서 과도하게 쓰지만 않으면 free plan으로도 이슈 없을 듯. (1월 1일에 리필되는지 확인 필요.)
- 의문
- 계층 구조에서는 service layer또는 application layer라 불리는 계층에 비즈니스 로직을 때려박는다.
- Flux 구조에서는 어디에 들어가야 하는가.
- component에 다 때려박으면 간단은 하다. 하지만 이는 BE에서 컨트롤러에 다 때려박는 것과 다를 바가 없어보인다. 컴포넌트가 너무 복잡해지면 코드 읽기가 어려울 듯 하다.
- reducer에 넣으면 되긴 하는데, 로직이 복잡해지면 reducer가 너무 커지지 않을까..
- 1안: component에 다 때려박기
- 2안: reducer에 넣기
- reducer는 순수함수로만 작성되어야 한다. 따라서 비동기처리가 불가능하다. 참고
- GPL
- 제약: GPL 라이센스 명시, 반드시 코드 전문 오픈소스로 공개
- 허용: 걍 다 됨.
- BSD (2-clause)
- 제약: 프로그램 원본에 실린 BSD 라이센스 전문 명시(이 때 원 저작자 정보가 같이 실리게 됨).
- 허용: 사용, 수정, 재배포
- 참고: FreeBSD, 3-clause, 4-clause도 있다. clause가 늘어날 수록 제약이 심해진다. 2-clause는 사실상 MIT 라이센스와 동일하다.
- MIT
- 제약: MIT 라이센스 명시(이 때 원 저작자 정보가 같이 실리게 됨)
- 허용: 가져다 쓰든 개조를 하든 소프트웨어의 복사본을 판매하든 걍 다 됨.
- Apache
- 제약: Apache 라이센스 명시, 아파치재단 샤라웃, 코드 수정시 외부에 그 사실을 밝혀야 함.
- 허용: 걍 다 됨. (아파치 라이센스 적용시에는 특허권 보호가 불가능함)