728x90
반응형
728x90
반응형
반응형

환경: springboot 2.7.3, /resources 안에 든 .xlsx 파일을 다운로드할 수 있는 api 제공

@GetMapping("/format")
public void downloadExcelFormat(HttpServletResponse response) throws Exception {
  try (InputStream inputStream = new ClassPathResource("mail_format.xlsx").getInputStream()) {
    response.setHeader("Content-Disposition", "attachment; filename=\"mail_format.xlsx\"");
    response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
    try (ServletOutputStream outputStream = response.getOutputStream()) {
      IOUtils.copy(inputStream, outputStream);
    }
  }
}
  • resources 경로의 파일은 ClassPathResource로 읽을 수 있음
  • 파일을 input stream으로 읽고 output stream으로 return

 

vue2에서 호출할 때는 아래와 같이 해야한다. 

getFormatDownload() {
ApiMail.getMailFormat().then((response) => {
  const url = URL.createObjectURL(
    new Blob([response.data], {
      type: 'application/vnd.ms-excel',
    })
  );
  const link = document.createElement('a');
  link.href = url;
  link.setAttribute('download', 'mail_format.xlsx');
  document.body.appendChild(link);
  link.click();
});
}
--------

export const getMailFormat = (): Promise<AxiosResponse> => {
const axiosOptions: AxiosOptionsParam = {
method: 'GET',
url: '/api/mail/format',
responseType: 'arraybuffer',
};

return asyncWithoutWrapper(axiosOptions);
};
--------

export async function asyncWithoutWrapper<T>(params: AxiosOptionsParam): Promise<AxiosResponse<T>> {
const axiosOptionsParams: AxiosRequestConfig = createAxiosOptions({
...params,
});
const response: AxiosResponse<T> = await axios(axiosOptionsParams);

return Promise.resolve(response);
}
  • 응답 값을 Blob으로 감싸고 형식이 excel인지 알려줘야 함
  • axios param에 responseType을 'arraybuffer' 로 세팅해야 함
728x90
반응형
반응형

환경: springboot 2.7.3

implementation 'org.apache.poi:poi:5.2.2'
implementation 'org.apache.poi:poi-ooxml:5.2.2'
implementation 'commons-io:commons-io:2.11.0'  //확장자 검사

화면 -> 웹서버로 파일 업로드를 할 때,

  • POST요청으로
  • rest api 사용(form submit 아님)
  • 파일(Multipartfile)과 본문 내용(RequestBody)을 동시에 보낼 경우 

 

@RequestPart 어노테이션을 사용하면 둘 다 한 요청으로 보낼 수 있다.

public BaseResponse sendFile(
    @Valid @RequestPart SendRequest request,
    @RequestPart MultipartFile csvFile)

 

다만 swagger(open-api)에서 테스트하는 경우 헤더 관련 에러가 나는데

아래와 같이 @Parameter 어노테이션으로 binary로 받겠다고 하고

@PostMapping(
    value = "/send-file",
    consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
@Operation(summary = "우편물 발송 > 파일 업로드", description = "우편 작성 > 우편 발송 api")
public BaseResponse sendFile(
    @Valid @RequestPart @Parameter(schema = @Schema(type = "string", format = "binary"))
        SendRequest request,
    @RequestPart MultipartFile csvFile) {

스웨거에서 body부분을 json 파일로 만들어 첨부하면 된다.


+ 추가

만약 vue/js 화면 등에서 이 api를 호출하려면?

@RequestPart에 해당하는 값을 formData에 넣어서 보내야한다. 

또한 헤더에 multipart/form-data를 실어야 한다.

아래는 vue2+ts 예시

export const sendFile = (params: SendRequest, file: File): Promise<Response<void>> => {
  const body = new FormData();
  body.append(
    'request',
    new Blob([JSON.stringify(params)], {
      type: 'application/json',
    })
  );
  body.append('file', file);

  const axiosOptions: AxiosOptionsParam = {
    method: 'POST',
    url: '/api/mail/send-file',
    data: body,
    header: {
      'content-type': 'multipart/form-data',
    },
  };

  return http<void>(axiosOptions);
};
728x90
반응형
반응형

환경: java11, springboot 2.5.6, hibernate-core 5.4.32

 

프로젝트를 작업하다 보면 변화하는 데이터 형태를 수용하기 위하여 sub data 콜롬에 json형태로 데이터를 저장하는 경우가 많이 있다. 화면에는 이 데이터를 꼭.. 보여달라는 요구사항이 많다 보니 mysql의 json관련 함수를 사용하게 될 일이 자주 생긴다.

주로 spring-data-jpa를 통해 데이터를 꺼내오는데 관련 쿼리를 작성하다 NPE를 만나 해결하는 과정을 작성한다.

 

nativeQuery=true일 경우는 기존 쿼리처럼 사용하면 된다.

 @Query(value = "select " +
            "JSON_UNQUOTE(JSON_EXTRACT(data, '$.strClazz')) as clazz, "+
            "JSON_EXTRACT(data, '$.totalScore') as totalScore "+
            "from tbl_user_event e " +
            "where e.gid = :#{#req.gid} " +
            "and e.event_id = :#{#req.eventId} " +
            "order by e.base_date desc " +
            "limit 1 ",
            nativeQuery = true)
Optional<HelloUserRes> getTop1HelloUserInfo(HelloEventReq req);

nativeQuery=true인 경우에 custom dto class로 바로 받기가 어려워 interface & projection의 형태 or map으로 데이터를 받아야 한다. 허나 개인적으로 이 방법을 안 좋아하므로.. 보통은 nativeQuery=false에 dto constructor를 사용하여 데이터를 꺼내는 편이다. 

 

nativeQuery=false일 경우, 'function'을 사용하여 Mysql(혹은 기타 사용하는 데이터베이스)에서 제공하는 함수를 사용할 수 있다고 하여 아래와 같이 작성하였다. 

 @Query(value = "select new com.hello.model.log.LoginHistoryResponse( " +
            "l.logDate, l.gid, l.eventId, l.logType, " +
            "FUNCTION('JSON_EXTRACT', l.extraData, '$.deviceName'), FUNCTION('JSON_EXTRACT', l.extraData, '$.clientIp'), FUNCTION('JSON_EXTRACT', l.extraData, '$.appVersion')) " +
            "from UserEventLog l " +
            "where l.eventId = :#{#request.eventId} " +
            "and l.gid = :#{#request.gid} " +
            "and l.logDate between :#{#request.startDate} and :#{#request.endDate} ")
Page<LoginHistoryResponse> findByEventIdAndGidAndLogDateBetween(LoginHistoryRequest request, Pageable pageable);

 

그런데 서버를 재시작하고 보니 아래와 같이 NPE가 발생하는 게 아닌가? 

Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract org.springframework.data.domain.Page com.....
	at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:96)
	at org.springframework.data.jpa.repository.query.SimpleJpaQuery.<init>(SimpleJpaQuery.java:66)
	at org.springframework.data.jpa.repository.query.JpaQueryFactory.fromMethodWithQueryString(JpaQueryFactory.java:51)
	at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$DeclaredQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:163)
	at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:252)
	at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:87)
	at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.lookupQuery(QueryExecutorMethodInterceptor.java:102)
	... 66 common frames omitted
Caused by: java.lang.NullPointerException: null
	at org.hibernate.hql.internal.NameGenerator.generateColumnNames(NameGenerator.java:27)
	at org.hibernate.hql.internal.ast.util.SessionFactoryHelper.generateColumnNames(SessionFactoryHelper.java:434)
	at org.hibernate.hql.internal.ast.tree.SelectClause.initializeColumnNames(SelectClause.java:268)
	at org.hibernate.hql.internal.ast.tree.SelectClause.finishInitialization(SelectClause.java:258)
	at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:253)
	at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:1028)
	at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:796)
	at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:694)
	at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:330)
	at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:278)
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:276)
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:192)
	at org.hibernate.hql.internal.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:144)
	at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:113)
	at org.hibernate.engine.query.spi.HQLQueryPlan.<init>(HQLQueryPlan.java:73)
	at org.hibernate.engine.query.spi.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:162)
	at org.hibernate.internal.AbstractSharedSessionContract.getQueryPlan(AbstractSharedSessionContract.java:613)
	at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:725)
	at org.hibernate.internal.AbstractSharedSessionContract.createQuery(AbstractSharedSessionContract.java:114)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
	at org.springframework.orm.jpa.ExtendedEntityManagerCreator$ExtendedEntityManagerInvocationHandler.invoke(ExtendedEntityManagerCreator.java:362)
	at com.sun.proxy.$Proxy129.createQuery(Unknown Source)
	at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:90)

 

쿼리 검증 시 발생한다는 건 알겠는데 왜 null 일까 뚜렷한 이유를 바로 알기 어려웠다. 느낌적으로 function()에서 발생한다는 촉이 발동하여 관련 자료를 더 찾아보았다.

찾아보니 Hibernate 5.2.18부터 MetadataBuilderContributor라는 인터페이스를 사용할 수 있고 이것을 구현해야 SQL Function을 사용할 수 있다고 한다?!

아래와 같이 구현하고 사용하고자 하는 함수를 add 해준다.

public class SqlMetaBuilderContributor implements MetadataBuilderContributor {
    @Override
    public void contribute(MetadataBuilder metadataBuilder) {
        metadataBuilder.applySqlFunction("JSON_EXTRACT", new StandardSQLFunction("JSON_EXTRACT", StringType.INSTANCE))
                .applySqlFunction("JSON_UNQUOTE", new StandardSQLFunction("JSON_UNQUOTE", StringType.INSTANCE))
                .applySqlFunction("JSON_CONTAINS", new StandardSQLFunction("JSON_CONTAINS", StandardBasicTypes.BOOLEAN))
                .applySqlFunction("STR_TO_DATE", new StandardSQLFunction("STR_TO_DATE", LocalDateType.INSTANCE))
                .applySqlFunction("MATCH_AGAINST", new SQLFunctionTemplate(DoubleType.INSTANCE, "MATCH (?1) AGAINST (?2 IN BOOLEAN MODE)"));
    }
}

그리고 이 contributor를 어떻게 주입할까 싶었는데, 아래와 같이 설정값을 추가해주면 된다.

spring.jpa.properties.hibernate.metadata_builder_contributor=com.common.config.SqlMetaBuilderContributor

 

++

nativeQuery=false 인 경우 '->>' 가 먹지 않아서.. JSON_UNQUOTE를 할 때 ->> 가 아닌 function('JSON_UNQUOTE', ~) 형식을 사용해야 한다.

즉, nativeQuery=true 인 경우는 아래와 같이 ->>가 사용 가능하나

@Query(value = "select sum(h.round1) as round1, sum(h.round2) as round2, sum(h.round3) as round3, sum(h.round4) as round4 " +
        "from " +
        "(select "+
        "if(JSON_LENGTH(l.data->>'$.weeks[0].achieves') != 0, 1, 0) round1, "+
        "if(JSON_LENGTH(l.data->>'$.weeks[1].achieves') != 0, 1, 0) round2, "+
        "if(JSON_LENGTH(l.data->>'$.weeks[2].achieves') != 0, 1, 0) round3, "+
        "if(JSON_LENGTH(l.data->>'$.weeks[3].achieves') != 0, 1, 0) round4 "+
        "from hd_user_event as l " +
        "where base_date = '1970-01-01 00:00:00' and event_id = 'hello') as h"
	, nativeQuery = true)
Map<String, Object> getHelloSummary();

nativeQuery=false 면 아래처럼 사용해야 한다..

@Query(value = "select new com.model.log.LoginHistoryResponse( " +
        "l.logDate, l.gid, l.eventId, l.logType, " +
        "FUNCTION('JSON_UNQUOTE', FUNCTION('JSON_EXTRACT', l.extraData, '$.deviceName')), FUNCTION('JSON_UNQUOTE', FUNCTION('JSON_EXTRACT', l.extraData, '$.clientIp')), FUNCTION('JSON_UNQUOTE', FUNCTION('JSON_EXTRACT', l.extraData, '$.appVersion')) ) " +
        "from UserEventLog l " +
        "where l.eventId = :#{#request.eventId} " +
        "and l.gid = :#{#request.gid} " +
        "and l.logDate between :#{#request.startDate} and :#{#request.endDate} ")
Page<LoginHistoryResponse> findByEventIdAndGidAndLogDateBetween(LoginHistoryRequest request, Pageable pageable);

매우 안 이쁘고(가독성이 떨어지고) 긴 쿼리가 아닐 수 없다...ㅠㅅ ㅠ

728x90
반응형

'개발 > spring' 카테고리의 다른 글

[web] file download inside jar  (0) 2022.11.02
[request part] file upload  (0) 2022.10.31
[transaction] why do we need read-only transaction?  (0) 2022.09.21
[spring-jpa] @Transactional saveAll  (0) 2022.08.03
[spring-jpa] stream vs list  (0) 2022.08.01
반응형

목표: 깨끗한 클래스 만들기

 

Class should be small

클래스 체계 / encapsulation

  • 클래스 항목의 순서

class

  • 캡슐화
    • public으로 선언된 변수는 거의 없음
    • 변수와 유틸리티 함수는 가능한 공개하지 않기
    • 테스트를 위해 protected/default로 선언하는 경우는 있음

 

클래스는 작아야 한다, 최소의 책임!

  • 클래스의 역할을 and, if, or, but을 사용하지 않고 25 단어 내외로 설명할 수 있어야

단일 책임 원칙(Single Responsibility Principle; SRP)

  • 변경해야 하는 이유가 하나뿐이어야 한다. -> 수정 이유가 같은 것들을 묶는다. -> 유지보수를 쉽게!
  • 작은 클래스 여러 개가 큰 클래스 하나보다 낫다.
  • 다른 작은 클래스와 협력하여 동작하도록

 

응집도(cohesion of class)

  • 클래스는 인스턴스 변수의 수가 적어야 한다.
  • 메서드가 인스턴스 변수를 더 많이 사용할수록 메서드와 클래스는 응집도가 높다.
  • 응집력을 잃는다면 클래스를 분리하라(응집도를 유지하면 작은 클래스가 여러 개 나온다).
  • 함수를 작게, 매개변수 목록을 짧게

 

쪼개라

변경하기 쉬운 클래스(가 되도록 쪼개라) 변경으로부터 격리(시키기 위해 쪼개라)
새 기능을 수정하거나 기존 기능을 변경할 때 건드릴 코드가 최소가 되게끔 다른 기능은 보존
특정 함수에서만 사용하는 private 메서드가 있다면 쪼갤 신호 결합도를 낮춰 테스트 코드 작성을 용이하게
한 클래스에 너무 많은 책임이 있지 않도록 변경이 심하거나 외부에 영향을 받는 로직은 추상적인 개념을 분리해 인터페이스(추상클래스)로 분리한다음 테스트 클래스를 만들어서 테스트

 

쪼개야 할 클래스의 예시로 clean code의 저자는 아래를 보여준다.

before

 

after

Sql은 추상 클래스로 분리하고 각각의 책임들을 클래스로 분리했다. 이로써 얻을 수 있는 이점은: 

  • 함수 하나를 수정했다고 다른 함수가 망가질 위험이 사라짐
  • 테스트 코드로 구석구석 증명하기도 쉬워짐
  • 새로운 기능인 Update를 추가하고 싶다면 Sql을 상속하는 UpdateSql 클래스를 생성하면 손쉽게 추가할 수 있음
  • OCP

 

클래스를 쪼개다 보면 (자연스레) 객체지향의 특징인 SOLID를 갖출 수 있다.

  • 기존 기능을 변경할 때 시스템을 확장할 뿐 기존 코드를 변경하지 않는다.
  • OCP(Open-Closed Principle) 개방-폐쇄 원칙: 
    • 확장에 대해 열려있고 수정에 대해서는 닫혀있어야 한다는 원칙
  • 변경으로부터 격리; 결합도를 낮추면 유연성과 재사용성이 높아진다.
  • DIP(Dependency Inversion Principle) 의존 역전 원칙:
    • 고수준 모듈은 저수준 모듈의 구현에 의존해서는 안 되며, 저수준 모듈이 고수준 모듈에서 정의한 추상 타입에 의존해야 한다는 것(추상화에 의존해야 한다)

참고) SOLID(객체 지향 설계)

728x90
반응형
반응형

목표: 올바른 객체/자료구조의 사용이 무엇인지 확인, 상황에 알맞게 사용하자.


객체(object) vs 자료구조(data structure)

  • 객체: 추상화 뒤로 자료(field)를 숨긴 채 자료를 다루는 함수만 공개
  • 자료구조: 자료를 그대로 공개하며 별다른 함수를 제공하지 않음
  • 잡종 구조(hybrid): 반반무마니 구조, 새로운 함수는 물론이고 새로운 객체를 추가하는 것도 어려움(양쪽의 단점만 모은 구조)
    -> 되도록 사용하지 않도록!

객체지향 코드(OOP) vs 절차지향 코드(procedural)

  객체지향 절차지향
특징 bottom-up
기존 함수를 변경하지 않으면서 새 클래스를 추가하기 쉬움
새로운 함수를 추가하기 어려움 -> 모든 클래스를 고쳐야
top-down
기존 자료구조를 변형하지 않으면서 새 함수를 추가하기 쉬움
새로운 자료구조를 추가하기 어려움 -> 모든 함수를 고쳐야
단점 1. 느린 개발 속도(설계시간)
2. 느린 실행/처리 속도
1. 유지보수의 어려움
2. 엄격하게 순서가 정해짐
3. 프로그램 분석이 어렵다
장점 1. 생산성/유지보수가 상대적으로 쉬움
2. 자원의 재사용성
1. 컴퓨터 처리 방식과 유사하기 때문에 실행 속도가 빠름
예시

: 어떤 스타일이 항상 맞고, 항상 정답이진 않는다. 각각 상황에 알맞은 설계를 가져가는 것이 핵심.

  • 새로운 자료타입을 추가하는 유연성이 필요? 객체지향적으로!
  • 새로운 동작을 추가하는 유연성이 필요? 절차지향적으로!

The Law of Demeter; 디미터의 법칙

Don't Talk to Strangers
Principle of least Knowledge

 

"객체가 어떤 데이터를 가지고 있는가?"           "객체가 어떤 메시지를 주고받는가?

: 구현을 모른 채 핵심을 조작할 수 있도록, 객체의 내부를 모르게 하라(결합도를 낮추자).

 

기차 충돌(train wreck)

여러 개의 dot을 사용하지 말라.

기차충돌 예시

 

그럼, 단순히 dot이 많다고 디미터의 법칙을 어긴 것인가?

: NO, stream or 일반적인 자료구조는 dot을 사용할 수밖에

  • 객체라면 내부 구조를 숨겨야 하기에 디미터 법칙을 위반한다.
  • 자료구조라면 이미 노출된 내부 구조를 사용하기에 디미터 법칙을 위반하지 않는다.

  • best practice: 뭔가를 하라고 해야지, 속내를 드러내면 안된다.

디미터 원칙 > 행위 노출


DTO: 공개된 변수만 있고 함수(비즈니스 로직)는 없는 클래스

활성 레코드: DTO + 탐색 함수(save, find); 자료 구조로 취급하라

-> 비즈니스 로직을 가지고 있지 않도록 주의!(잡종 구조가 되어버린다)


참고: 객체지향 vs 절차지향 

https://usefultoknow.tistory.com/entry/%EC%A0%88%EC%B0%A8%EC%A7%80%ED%96%A5Procedural-Programming-%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5Object-Oriented-Programming-%EC%9E%A5%EB%8B%A8%EC%A0%90-%EB%B0%8F-%EC%B0%A8%EC%9D%B4%EC%A0%90

728x90
반응형
반응형

나는 단순히 읽기(select)만 하는데 왜 transaction이 필요할까? 그런거 없어도 잘.. 되는데?

@Transactional(realOnly = false)

 

1. 안전한 읽기, 효율적인 읽기

우리가 따로 선언을 하지 않아도, DB 접속을 하면 우리가 별도의 설정을 안했어도 나름의 기본적인(physical) 트랜젝션을 생성한다. 이걸로 그냥 select하면 되지 왜 선언을 해야할까? 사실 그렇게 생성된 트랜젝션은 우리가 어떤 일을 할지 모르므로 변경을 위한 준비까지 해 놓는다. 즉, 우리는 10만큼만 필요한데 100만큼 준비하는 결과를 가져옴. 그래서 어플리케이션에게 우리 10만 쓸거니까 조금만 준비해~ 라고 알려줘서 성능의 향상을 가져온다.

  • 변경 감지를 위한 추가적인 스냅샷을 저장하지 않아 성능 향상(메모리를 적게 사용가능)
  • 커밋이나 롤백이 있지 않는 한 data pool을 들고 있는 경우가 있는데(자원을 반납하지 않음), read라고 알려주면 자원을 효율적으로 관리할 수 있음

참고: https://willseungh0.tistory.com/75

 

2. 데이터 구조 변경 시 안전함 보장

1번에서 말한 것과 같이 스냅샷을 저장하지 않기에 dirty checking도 일어나지 않는다. 개발자의 실수로 인한 entity set이 일어나도 저장이 되지 않으니, 그나마 안전하달까..

또한 오래걸리는 조회의 경우, 조회 결과를 만들면서 데이터가 수정되어 out될 때는 다른 결과를 갖게될 수도 있다. 데이터의 내용은 그렇다쳐도 혹시 데이터의 구조까지 변하게 되면 추출을 위해 몇시간을 기다렸는데 에러가 나면서 무효가 될 것이다.. 이 때 transactional을 걸어두면 transaction이 생성되는 시점의 데이터로 freeze해줘(스냅샷 생성) 일관성있는 데이터를 return받을 수 있다(데이터 구조의 변경이 있어도 영향을 받지 않아 안정적으로 데이터를 받을 수 있음).

참고: http://dba.fyicenter.com/faq/oracle/What-Is-READ-ONLY-Transaction.html#:~:text=A%20READ%20ONLY%20transaction%20is,the%20end%20of%20the%20transaction.

 

참고) 단건의 insert/update/delete 같은 경우는 transactional이 굳이 없어도 save안에 선언된 transactional로 영속성을 유지가능, 그러나 다수의 수정이 있다면 transactional을 함수레벨에 별도로 달아서 하나의 트랜젝션으로 관리할 필요있음

728x90
반응형
반응형

docker vs kubernetes

docker 도커

  • 도커는 환경이 다른 곳에도 같은 환경을 유지할 수 있도록 하는 기술(개발환경과 배포 환경을 동일하게 관리할 수 있음)
  • 1 docker - n isolated containers; 하나의 같은 서버에서 각기 다른 환경의 컨테이너를 설정 가능
  • 원하는 환경을 파일에 저장하면 도커는 어떤 머신에든 해당 환경을 simulate 해줌
  • 이런 환경들은 각기 독립적으로 존재, 어떠한 환경이든 모듈식으로 관리 가능
  • dockerfile(컨테이너를 어떻게 만들어야 하는지 설명서) -> image 생성(세팅 포함; 스냅샷; 변경 불가 불변) -> 컨테이너로 배포(고립된 환경 안에서 실행)
  • 로컬 pc에서 이미지를 PUSH -> container registry에 저장되고 -> 이걸 서버에서 PULL받아서 실행(서버에도 도커와 같이 container engine 설치되어 있어야)

 

kubernetes 쿠버네티스

  • 여러 컨테이너가 있을 때 편하게 관리하게 할 수 있는 도구
  • 어떻게 운영할지 자원으로 정의
  • 자동 확장/축소/zero time patch/모니터링/로드밸런싱 등 제공

 

container vs vm

https://youtu.be/oKri6LxuPUg

   vm 가상서버 container
size 거대한 이미지 사이즈
재사용성 낮음
작은 이미지 사이즈
레이어 개념으로 이미지에 파일을 추가/삭제하여 관리
레이어 사이즈를 최적화하여 이미지 사이즈를 최소화
starting time 느린 부팅시간
hypervisor -os -middleware- 애플리케이션 실행되어야 함 
빠른 시작 시간
os 부팅이 필요없기 때문에(host os 공유) 부팅 시간 최소화(프로세스 시작)
env vm간 환경 불일치
vm생성 후 개별로 변경사항을 관리하기 때문에 vm 간 구성이나 환경이 불일치
높은 이동성
애플리케이션에 필요한 라이브러리나 의존파일을 이미지에 포함하기 때문에 환경에 의한 문제가 거의 없음
  수동확장/수동복구 자동확장/자동복구
728x90
반응형
반응형

jdk가 무료 버전이 있고 유료버전이 있는데, 사용하던 jdk 11, 17이 oracle jdk인 듯하여 openJdk로 교체해본다.

https://auramin.tistory.com/25

 

오라클 Java SE(Standard Edition) 유,무료 버전

오라클 정책 변경으로 유료버전의 오라클 Java SE(JDK:Java Development Kit/  JRE:Java Runtime Environment)를 다운로드 또는 업데이트 할 경우 라이센스 계약 및 비용을 지불해야한다 합니다. 오라클 정책을..

auramin.tistory.com

 

현재 나의 jdk현황은 아래와 같다.

 

1.7.0_80은 무료 버전이라 그냥 두기로 하였고

1.7.80.15 버전은 지워야 할 것 같아서 아래와 같은 명령어를 통해 삭제 후, 1.7.0_80 버전을 심볼릭으로 걸어주었다.

sudo rm -fr /Library/Internet\ Plug-Ins/JavaAppletPlugin.plugin
sudo rm -fr /Library/PreferencesPanes/JavaControlPanel.prefPane
sudo rm -fr ~/Library/Application\ Support/Oracle/Java

-----------------
sudo rm -rf “/Library/Internet Plug-Ins/JavaAppletPlugin.plugin”
sudo ln -s /Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk /Library/Internet Plug-Ins/JavaAppletPlugin.plugin

 

openJdk11, 17 버전은 mac을 사용하면 brew 명령어를 통해 설치가 가능하나, 시도해본 결과 의도한 대로 잘 되지 않아서 uninstall 하고 수동으로 설치하였다.

설치 파일은 아래 사이트에서 다운로드하였고 zip을 풀어준다.

https://jdk.java.net/archive/

 

Archived OpenJDK GA Releases

Archived OpenJDK General-Availability Releases This page is an archive of previously released builds of the JDK licensed under the GNU General Public License, version 2, with Classpath Exception. WARNING: These older versions of the JDK are provided to he

jdk.java.net

https://adoptium.net/marketplace

 

아래처럼 기존 자바를 지우고

cd /Library/Java/JavaVirtualMachines
sudo rm -rf jdk-17.0.3.1.jdk

zip을 풀어준 폴더를 아래 경로로 옮긴다.

sudo mv jdk-17.0.2.jdk/ /Library/Java/JavaVirtualMachines/

 

잘 되었는지 확인

/usr/libexec/java_home -V

 

참고로 잘못된 파일을 받은 경우(나의 경우는 Mac/AArch64를 받았었다) 위 명령어로 잡히지 않는다.

success?

어쨌건 위 명령어로 잘 깔린 것을 확인하고 intellij의 프로젝트를 빌드해보는데.. 아래와 같은 에러가 난다.

mac 시스템 환경설정 > 보안 및 개인 정보보호 메뉴로 가서 팝업창 하단 아래쪽에 뜬 아래 문구를 허용을 눌렀다.

확인된 개발자가 등록한 응용 프로그램이 아니기 때문에 ~~ 사용을 차단했습니다. 

하지만 그래도 gradle clean 조차 failed 하길래 아래와 같이 우선 owner설정을 바꾸고(이게 무슨 의미가 있는지는 확실하게 모르겠다. 단지 기존 jdk가 저렇게 설정이 되어 있어서 똑같이 맞춰봤다)

sudo chown -R root:wheel ./jdk-17.0.2.jdk

 

intellij에서 project close -> reopen -> invalid cache를 하니 정상적으로 실행되었다..

 


참고

https://engineering.linecorp.com/ko/blog/line-open-jdk/

 

728x90
반응형

'개발 > java' 카테고리의 다른 글

[google admob] ssv 콜백 적용  (0) 2023.01.06
[Date] java8 이하에서 날짜 timezone 변환  (0) 2022.11.04
[java] for loops and performance  (0) 2022.08.04
[jmh] benchmark test  (0) 2022.08.04
[powermock] mock static method  (0) 2022.07.21
반응형

팀 내에서 코드 리뷰를 하고 있지만 어떻게 하는 것이 서로에게 좋은 시너지를 낼 수 있는 방법인지 확신이 서지 않을 때가 많았다. 누구 하나 가이드를 주지 않고 작성하다 보니 종종 의도하지 않은 상처를 준다거나 상처를 받았던 경우가 생기는 것 같아 더 나은 협업을 위한 방법을 모색하게 된다.

아래 영상은 그 일환으로 시청했던 것인데 매우 유익하고 공감 가는 내용이 많아서 정리하려고 한다.

 

1. 효율적인 코드 리뷰하기

코드 리뷰의 어려움:

  • 생각을 글로 전하는 것에 대한 어려움, 피드백을 조심스럽게 표현하는 것이 더 중요.

 

팀 내의 노력:

  • 지루한 작업은 컴퓨터로 처리(공백, 들여 쓰기 등)
  • 스타일 가이드를 통해 스타일 논쟁 해소
  • 모두를 포함하라

 

저자의 노력:

  • 의미 있는 커밋으로 분리(나중에 나의 코드조차 낯설다, 커밋도 로그, 잘 정리하면 미래에 모두에게 도움이 된다)
  • pr 올릴 때 주석 달기
  • 저자가 먼저 읽어보고 -> 리뷰어들을 위한 설명을 코멘트로 남겨서 -> 리뷰어들의 시간을 절약할 수 있게 하라
  • pr은 적게(반나절 작업한 정도) 자주 올리는 게 좋다(리뷰할 시간이 없다고 생각하면 안 됨)

 

리뷰어의 노력:

  • 리뷰는 즉시 시작하라
  • 고수준 피드백(버그, 장애, 성능, 보안)에 대해 먼저 잡고 -> 저수준 피드백(설계 개선, 변수명, 주석 등)을 남기자
  • 리뷰 라운드에서 너무 많은 의견을 남기면 저자가 당황할 수도.. 한 라운드에 20~50개는 위험의 시작.
  • 한 두 등급만 코드 레벨을 올리는 것을 목표로, 완전하지는 않아도 충분히 좋은 코드가 되도록
  • Nit 태그: 고치면 좋지만 아니어도 그만, 개선할 수 있는 의견을 자유롭게 남길 수 있어야 함

 

피드백 방법:

  • 진정한 칭찬이 필요 -> 우리는 잔인한 감시자가 아니라 도와주려는 팀 동료
  • 피드백은 명령이 아니라 요청(나의 걱정): 하세요 XXX ~하는 게 어떨까요? OOO
  • 원칙에 기반한 제안(~에 맞으려면 이렇게 하는 게 더 좋을 것 같아요)
  • 항상 원칙에 기반하여 설명할 수 있는건 아니다. 이럴 땐 무엇을 할 수 있을지 객관적인 설명이 필요하다.
  • 반복적인 이슈는 다 말할 필요 없다(2~3개의 예시를 언급하고 그 이상은 비슷한 패턴에 대해 수정 요구)
  • 그러려니~ 하는 마인드 필요, 우리는 싸우려고 코드 리뷰하는 게 아니다. 동료와의 협업이 훨씬 중요한 것이다.
  • 팀을 유지하기 위해 불완전한 해결책을 받아들여라. 모든 결함이 항상 문제가 되지는 않음
  • 코드 리뷰의 목적은 비난이 아니라 배움이다.

 


 

2. git commit message structure

기본적으로 커밋 메시지는 아래와 같이 제목(subject), 본문(body), 꼬리말(footer)로 구성된다.

<타입>[적용 범위(선택 사항)]: <설명>

[본문(선택 사항)]

[꼬리말(선택 사항)]
  • <설명>은 50자 이내로 작성, 마침표 찍지 않는다.
  • 타입에는 아래와 같은 값들이 올 수 있다.

commit types

  • body는 선택사항으로 모든 커밋에 작성할 필요는 없지만 부연설명이 필요할 경우 72자 이내로 작성한다. 제목과 구분하기 위해 한 칸 띄어 쓰고 시작한다.
  • footer는 issuer trackerId(이슈 문서 id 등)를 기입하는데 주로 사용된다.

 

참고: 더 디테일한 commit message

https://medium.com/humanscape-tech/%ED%9A%A8%EC%9C%A8%EC%A0%81%EC%9D%B8-commit-message-%EC%9E%91%EC%84%B1%EC%9D%84-%EC%9C%84%ED%95%9C-conventional-commits-ae885898e754

 

효율적인 commit message 작성을 위한 conventional commits

안녕하세요 휴먼스케이프 개발자 Jake 입니다.

medium.com

https://haesoo9410.tistory.com/299

 

<Git> 커밋 메시지 컨벤션 : 중요성 및 규칙 (feat. 템플릿)

1. 커밋 메시지 * 중요성  - 다음은 자바 스프링의 오래된 커밋 로그이다. $ git log --oneline -5 --author cbeams --before "Fri Mar 26 2009" e5f4b49 Re-adding ConfigurationPostProcessorTests after its..

haesoo9410.tistory.com

 

2-1. 커밋 시 자동으로 commit format 띄우기

아래와 같이 format 파일을 만들고 커밋을 친다. 이름은 아무거나 해도 되는데 나는 /.github/.gitmessage 로 저장하였다.

# 제목은 대문자로 시작합니다.
# 제목에는 타입,콜론,스페이스로 시작합니다.
# ex. Fix: typo in baduk.js
# 본문과 푸터는 선택 사항 입니다.
#######제목#######
Add: commit format
#######본문#######

#######푸터#######

######### 타입 #########
# Fix : 수정
# Add : 추가
# Remove : 삭제
# Simplify : 단순화
# Update : 보완
# Implement : 구현
# Prevent : 방지
# Move : 이동
# Rename : 이름변경
##################

 

아래와 같이 템플렛으로 지정한다는 명령어를 터미널에 입력한다.

git config --global commit.template .github/.gitmessage editor
## --global 옵션을 주면 전 프로젝트에 적용된다.

git config --unset-all --global commit.template
## 템플렛 취소

commit.template로. gitmessage라는 파일을 사용한다는 뜻이고, 마지막에 editor는 커밋할 때마다 editor가 떠서 메시지를 수정하게 한다는 뜻이다(vim으로 지정해도 된다).

이를 활용하여 커밋하려고 하면 터미널에 아래와 같은 순으로 작업한다.

git add [파일명]
git commit
## //-> 이러면 premade한 에디터가 뜨고 메세지를 수정 후 :wq 로 저장하고 나온다.
git push

이 방법의 문제는

  1. 개개인별로 설정해야 한다. 팀에 일괄 설정 안 됨
  2. 터미널에서밖에 안된다. git kraken이나 sourcetree 같은 툴에서는 주석이 먹지 않아서.. 매우 번거로워진다.

 

참고로 한글로 메시지를 쓸 경우 깨지는 경우가 허다.. 해서 아래와 같은 추가 노력이 필요할 수 있다.

https://velog.io/@haejung/Git-%EC%BB%A4%EB%B0%8B-%EB%A9%94%EC%8B%9C%EC%A7%80%EB%A5%BC-%EC%9C%84%ED%95%9C-Editor-%EC%84%A4%EC%A0%95-5072

 

Git 커밋 메시지를 위한 Editor 설정 (50/72)

앞서 소개했던 50/72 규칙에 맞는 커밋 메시지 작성을 조금더 수월하게 하기 위한 Editor 설정을 소개합니다. 아래 다른 블로그의 내용을 참조했습니다.Mac OSGitVSCode (커밋 메시지 Editor)D2Coding 글꼴커

velog.io


 

3. git pr format 생성

좋은 코드 리뷰란?
저자가 고생해서 리뷰어의 시간을 아껴줘야

https://www.youtube.com/watch?v=TAPviNhFuSg

  • git 자동으로 pr format 생성하는 법
  • pull_request_template.md 파일을 아래 링크처럼 생성하고
  • 이 파일을 git의 default branch(보통 master or main)에 커밋을 해야! 그다음부터 적용된다.

https://docs.github.com/en/communities/using-templates-to-encourage-useful-issues-and-pull-requests/creating-a-pull-request-template-for-your-repository

 

Creating a pull request template for your repository - GitHub Docs

For more information, see "About issue and pull request templates." You can create a PULL_REQUEST_TEMPLATE/ subdirectory in any of the supported folders to contain multiple pull request templates, and use the template query parameter to specify the templat

docs.github.com

 

728x90
반응형
반응형

https://learning.postman.com/docs/designing-and-developing-your-api/mocking-data/setting-up-mock/

 

Setting up mock servers | Postman Learning Center

Setting up mock servers: documentation for Postman, the collaboration platform for API development. Create better APIs—faster.

learning.postman.com

 

화면을 개발할 때 백엔드 api를 기다리며 개발을 홀딩하다가 api가 배포되면 그제야 화면에 연동하는 작업을 하게 되는 경우가 많다. 물론 이 과정도(실제 api와 연동) 필요한 과정이지만 기다리는 시간이 낭비된다. 이때 api spec만 알면 간편하게 목 서버를 만들어서 개발하다가 api가 배포되면 url만 교체해서 확인하면 시간을 절약할 수 있다.

목 서버를 만드는 방법은 위 링크처럼 여러 가지가 있지만 나는 collections에 샘플 req/res를 만들어놓고 mock server로 전환하는 편이다.

이때 아래와 같이 샘플 response를 미리 저장해야 한다. 각 api에서 add example을 통해 등록한다.

그리고 콜랙션을 목서버로 만든다. info를 누르면 목 서버를 만들 수 있는 창이 뜬다.

이때 url을 환경변수로 설정해두면 목서버를 만들 때 해당 목 서버가 자동으로 환경변수로 등록되어 url 전환이 쉬워진다. 또한 나중에 api가 다른 서버에 배포되었을 때 환경변수로 url을 등록만 해놓는다면 바로 전환해서 요청이 가능하므로 매우 유용하다.

 

환경변수는 왼쪽 environments 탭에서 설정 가능하다.

 

추가적으로 만든 목 서버/collection에 대한 api doc을 발행할 수도 있다.

documentation -> publish 하면 발행이 되고 최종 발행이 되면 url 생성된다. 이 url로 다른 사람도 나의 목 서버의 스펙을 확인, api를 요청할 수 있다(private 한 발행도 가능하다).

728x90
반응형

+ Recent posts