스프링
[스프링부트] CRDT 실시간 협업 에디터 만들기 - 삽입, 삭제
예전부터 실시간 협업 툴에 관심이 많았어서 무작정 구현해봤다. 1. 구현 방식각 문자를 하나씩 배열에 저장.각 문자는 실제 문서상 위치와는 별도로 자료구조 상 위치를 가짐. 이 위치는 소수로 표현할 것임.소수가 double형이 표현 가능한 자릿수 이상이 되는 것을 방지하기 위해 주기적으로 위치 재할당함. 삭제 연산은, 실제로 문자를 삭제하는 게 아니라 마킹만.각 문자를 하나씩 자료구조에 저장할 건데, 성능을 위해서는 배열보단 트리가 낫다. 하지만 처음 해보는 거라 배열로 구현할 것이다. 또한 문자 삭제 연산은 실제로 문자를 삭제하는 게 아니라 마킹만 할 것이다. 실행취소(undo)를 쉽게 구현하기 위해서이다. 2. 도메인 정의@Getter@AllArgsConstructorpublic class Ident..
[스프링] 배치 멱등성 보장하는 과정
1. 배치 멱등성 보장 방식내 경우에 배치가 같은 날 두 번 이상 실행되면 곤란해지는 문제가 있었다. 결제 데이터와 PG사의 거래 데이터를 비교해 불일치를 찾아 거래 오류 데이터를 생성하는 배치인데, 이 배치는 두 번 실행되면 거래 오류 데이터가 중복 생성된다. 사실 결제 데이터처럼 중복 생성된다고 큰일나는 건 아니지만 아무래도 오류 해결 여부를 식별할 때 방해가 될 것이다. 이런 문제에 대해 여러가지 해결책이 있다.설계 고치기: 애초에 배치가 여러 번 실행되어도 결과가 동일하도록 설계하기이상적이긴 한데 배치 구현하는 것만으로도 이미 힘듦생성된 데이터 삭제: 같은 날에 이미 실행되었다가 멈춘 작업이 존재한다면 생성된 데이터를 모두 삭제하기구현하기 쉬움배치 처리 여부 필드를 엔티티에 추가: 배치 처리를 ..
![[스프링] 책임 연쇄 패턴으로 요금 정책 구현하기](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOjKCc%2FbtsFDA2zPWw%2FJX3ivM6xsxXFdFTdAxtN01%2Fimg.png)
[스프링] 책임 연쇄 패턴으로 요금 정책 구현하기
스프링 ATDD 강의에서 주어진 미션을 수행하며 책임 연쇄 패턴을 직접 활용해 거리에 따른 요금 정책을 구현했다. 1. 요구사항 경로 조회 시 해당 경로에 대한 요금을 포함해 리턴해야 한다. 요금 정책은 다음과 같다. 기본 운임(10km 이내): 1,250원 이용 거리 초과 시 추가 운임 부과 10km 초과 ~ 50km 까지: 5km 당 100원 50km 초과 시: 8km 당 100원 9km = 1250원 12km = 10km + 2km = 1350원 16km = 10km + 6km = 1450원 2. 대충 구현하기 제일 쉽게 구현하는 방법은 if-else문을 쓰는 것이다. public class FareCalculator { public static int calculateOverFare(int dis..
![[스프링] 복잡한 비즈니스 로직 추상화](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVC0sF%2FbtsFnXXbMuD%2FY6AiuVBew6yeYy8GqBWkVk%2Fimg.png)
[스프링] 복잡한 비즈니스 로직 추상화
스프링 ATDD 강의를 수강하며 주어진 미션을 구현하면서 복잡한 비즈니스 로직을 추상화한 과정을 기술하겠다. 1. 요구사항 지하철 노선(Line)에 구간(Section)이, 구간에 역(Station)이 포함되어 있다. 노선은 구간 리스트를 갖는다. 하나의 구간에는 상행역, 하행역이 포함되어 있다. 처음에는 구간을 노선의 맨 뒤에 추가하는 것이 요구사항이었다. 맨 뒤에 추가하는 것은 쉽다. 코드 한 줄이면 된다. sections.add(newSection); 그 다음의 요구사항은 구간을 어느 위치에서나 추가할 수 있게 하는 것이었다. 어느 위치에서든지라는 조건은 세 가지로 나뉜다. 처음에 추가하기: 쉬움 sections.add(0, newSection); 중간에 추가하기: 어려움 sections.add(?..
![[스프링 해부] 컨트롤러 메소드의 인자 타입 다양하게 받도록 설계](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFEfOo%2FbtsBkjitK3v%2FZLxNlCU8Lx76Qbj255bCy0%2Fimg.png)
[스프링 해부] 컨트롤러 메소드의 인자 타입 다양하게 받도록 설계
저번 게시글에서 HandlerAdapter까지 구현했다. 커스텀 HandlerAdapter와 실제 RequestMappingHandlerAdapter는 큰 차이점이 하나 있다. 실제로는 다양한 인자 타입과 리턴 타입을 지원하기 위해 더 복잡한 로직이 필요하다. 이를 ArgumentResolver와 ReturnValueHandler로 해결할 수 있다. 이 부분은 자바 웹 프로그래밍 Next Step에 나와있지 않은 내용이라 고민이 많았다. 그래서 실제 스프링의 구조를 많이 참고했다. 1. 요구사항 @Controller public class UserController { @RequestMapping("/user/login") public String login(@RequestParam("id") Strin..
![[스프링 해부] HandlerAdapter로 컨트롤러 메소드 호출 통합하기](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmjdh0%2FbtsBo6QHj2S%2FKqKvDOhfv2tvABnFGwE9nK%2Fimg.png)
[스프링 해부] HandlerAdapter로 컨트롤러 메소드 호출 통합하기
저번 게시글에서는 @RequestMapping을 통해 컨트롤러 메소드가 호출되도록 구현하고 HandlerMapping 인터페이스를 만들어 리팩토링하는 것까지 살펴봤다. 하지만 아직 리팩토링 할 것이 남아있다. 1. 요구사항 @WebServlet(name = "dispatcher", urlpatterns = "/", loadOnStartup = 1) public class DispatcherServlet extends HttpServlet { private List mappings = new ArrayList(); @Override public void init() throws ServletException { lhm = new LegacyHandlerMapping(); lhm.initMapping(); ..
![[스프링 해부] @RequestMapping 구현하기](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcel4H9%2FbtsBjbYZNMt%2FwPzGyekY48iEpFGgnwMjYK%2Fimg.png)
[스프링 해부] @RequestMapping 구현하기
저번 게시글에서는 @RequestMapping 없이 직접 컨트롤러로 요청이 매핑되도록 구현해봤다. 이 방식의 단점은, 컨트롤러를 추가할 때마다 매번 RequestMapping 클래스에서 컨트롤러 클래스를 생성해야 한다는 점이다 . 이번에는 컨트롤러 클래스에 @Controller 어노테이션을 붙이고, 메소드에 @RequestMapping을 붙이면 알아서 요청이 그쪽으로 매핑되도록 구현해볼 것이다. 저번보다 훨씬 과정이 복잡하다. 자바 리플렉션도 이용해야 한다. 1. 요구사항 저번에 구현했던 Controller 인터페이스를 구현하는 컨트롤러 방식도 지원하면서 @RequestMapping 방식도 새롭게 추가하도록 구현할 것이다. 2. 흐름 전체적인 흐름은 다음과 같다. @Controller 어노테이션 붙은 컨..
![[스프링 해부] @RequestMapping 없이 구현하려면](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHcC6a%2FbtsBjdJcbUK%2FCjsLGkvU5MKBbqOik8eqkk%2Fimg.png)
[스프링 해부] @RequestMapping 없이 구현하려면
스프링에서는 @RequestMapping으로 편하게 요청을 분류해 처리할 수 있다. 원래는 DispatcherServlet이 요청을 처리할 수 있는 컨트롤러를 찾아서 위임을 해주는데, 내게 이 부분은 블랙박스였다. 내 시야에서는 클라이언트의 요청이 도착하는 곳이 컨트롤러였다. 그 블랙박스를 파헤쳐보려고 @RequestMapping 없이 컨트롤러로 요청을 받아 처리하는 코드를 자바 웹 프로그래밍 Next Step을 참고해 구현해보겠다. 세부 구현보다는 큰 그림(?)에 초점을 맞출 것이다. 1. 요구사항 @Controller, @RequestMapping을 사용하지 않을 것이다. Controller라는 커스텀 인터페이스를 만들고, 컨트롤러들이 이 인터페이스를 구현하게 하고, DispatcherServlet이..
![[스프링부트] 트랜잭션에서 외부 API 호출하는 문제에 대해](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2Zsbe%2FbtsAjVCteu4%2Fqpxs0KesQDs6KLesrHTwCK%2Fimg.png)
[스프링부트] 트랜잭션에서 외부 API 호출하는 문제에 대해
1. 첫 번째 문제: 외부 DB와 내부 DB 상태 불일치 1. 문제 상황 선물에 쌓인 후원금을 정산하는 로직에서 내 DB에 저장하는 것과 외부 API를 호출해서 실제로 입금이체를 하는 것 두 작업이 필요하다. 나는 이를 한 트랜잭션에서 수행하게 했다. 그런데 문제는, 내 DB에 입금이체 관련 정보를 저장하는 작업이 실패해도 외부 API 호출은 롤백되지 않는다는 것이다. 입금이체 취소 요청은 훨씬 복잡해서 할 수 없다. 이렇게 되면 입금이체가 성공했어도 내 DB는 그것을 알지 못하기 때문에 두 DB간의 불일치가 발생한다. 아직 입금이체가 되지 않은 선물로 보고 다음날에 다시 입금이체를 시도할 것이다. @Transactional(propagation = Propagation.REQUIRES_NEW) publ..
Spring WebFlux + OAuth2 로그인 구현 과정
스프링 MVC로 할 때 OAuth2 로그인 구현이 쉬웠던 건 참고 자료가 많기 때문이었다. 웹플럭스는 자료가 거의 없었다. 그래서 내부 동작을 모르면 따라치는 것만으로는 구현할 수 없었다. 그 과정을 기록한다. 1. OAuth2 구현 위한 전체적인 흐름 예상 스프링 시큐리티에서 자동으로 해주는게 많아서 원래라면 몇 번 요청을 주고받아야 했던 것을 설정 파일 등록으로 거의 해결이 된다. "{baseUrl}/login/oauth2/authorization/google"로 가면 구글 로그인 페이지가 뜸. 계정을 클릭해서 로그인 하면, 여긴 블랙박스 아마 처음에는 구글 쪽에서 code를 리턴하고, 그 code로 액세스 토큰, 리프레시 토큰을 요청하는 것 같음 여차저차 해서 승인된 리다이렉트 url인 "{base..