표현 영역의 코드는 폼에 입력한 요청 파라미터 값을 사용해서 응용 서비스가 요구하는 객체를 생성한 뒤, 응용 서비스의 메서드를 호출한다.
@RequestMapping(value = "/member/join")
public ModeMndView join(HttpServletRequest request) {
String email = request.getParameter("email");
String password = request.getParameter("password");
// 사용자 요청을 응용 서비스에 맞게 변환
JoinRequest joinReq = new JoinRequest(email, password);
// 변환한 객체(데이터)를 이용해서 응용 서비스 실행
joinService.join(joinReq);
}
// 도메인 객체 간 흐름 제어 역할
public Result doSomeFunc(SomeReq req) {
// 1. 리포지터리에서 애그리거트를 구한다.
SomeAgg agg = someAggRepository.findById(req.getId());
checkNull(agg);
// 2. 애그리거트의 도메인 기능을 실행한다.
agg.doFunc(req.getValue());
// 3. 결과를 리턴한다.
return createSuccessResult(agg);
}
// 새로운 애그리거트 생성 시 동작
public Result doSomeCreation(CreateSomeReq req) {
// 1 데이터 중복 둥 데이터가 유효한지 검사한다.
checkValid(req);
// 2. 애그리거트를 생성 한다.
SomeAgg newAgg = createSome(req);
// 3. 리포지터리에 애그리거트를 저장한다.
someAggRepository.save(newAgg);
// 4. 결과를 리턴한다.
return createSuccessResult(newAgg);
}
public class MemberService {
// 각 기능을 구현하는 데 필요한 리포지터리, 도메인 서비스 필드 추가
private MemberRepository memberRepository;
public void join(MemberJoinRequest joinRequest) { ... }
public void changePassword (String menberId, String currentPw, String newPw) { .... }
public void initializePassword(String memberId) { ... }
public void leave(String memberId, String curPw) { ... }
...
// 회원이 존재하지 않는 경우 Exception 발생시키는 로직
private Member findExistingMember(String memberId) {...}
}
// 각 응용 서비스에서 공동되는 로직을 별도 클래스로 구현
public final class MemberServiceHelper {
public static Member findExistingMember(MemberRepository repo, String memberId) {
Member member = memberRepository.findById(memberId);
if (member == null) throw new NoMemberException(memberId);
return member;
}
}
// 공통 메서드 import 하고 응용 서비스에서 사용.
import static MemberServiceHelper.*;
public class ChangePasswordService {
private MemberRepository memberRepository;
public void changePassword(String memberId, String curPw, String newPw) {
Member member = findExistingMember(memberRepository, menberId);
member.changePassword(curPw, newPw);
}
}
HttpServletRequest
나 HttpSession
을 응용 서 비스에 파라미터로 전달하면 안 된다.@Controller
@RequestMapping("/member/changePassword")
public class MemberPasswordController {
@RequestMapping(method = RequestMethod.POST)
public String submit(HttpServletRequest request) {
try { // 응용 서비스가 표힌 영역에 대한 의존이 발생하면 안 됨!
changePasswordService.changePassword(request);
} catch (NoMemberException ex) {
// 적절한 exception 처리 및 응답
}
}
...
}
HttpSession
이나 Cookie는 표현 영역의 상태에 해당하는데 이 상태를 응용 서비스에서 변경해 버리면 표현 영역의 코드만으로 표현 영역의 상태가 어떻게 변경되는지 이해하기 어려워진다. 즉, 표현 영역의 응집도가 깨지는 것이다. 이는 결과적으로 코드를 유지보수하는 비용을 증가시키는 원인이 된다.@Transactional
등 제공되는 트랜잭션 관리 기능을 이용해서 손쉽게 트랜잭션을 처리할 수 있다.@Transactional
이 적용된 메서드에서 RuntimeException
이 발생하면 rollback하고 그렇지 않으면 commit.public class InitPasswordService {
@Transactional
public void initializePassword(String memberId) {
Events.handle((PasswordChangedEvent evt) -> {
// evt.getId() 에 해당하는 회원에게 이메일 발송하는 기능 구현
});
Member member = memberRepository.findById(memberId);
checkMemberExists(member);
member.initializePassword();
}
}
package org.springframework.validation;
public interface Validator {
boolean supports(Class<?> var1);
void validate(Object var1, Errors var2);
}
@RequestMapping(value = "/orders/order", method = RequestMethod.POST)
public String order(@ModelAttribute("orderReq") OrderRequest orderRequest,
BindingResult bindingResult,
ModelMap modelMap) {
User user = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
new OrderRequestValidator().validate(orderRequest, bindingResult);
orderRequest.setOrderer(createOrderer(user));
if (bindingResult.hasErrors()) {
populateProductsModel(orderRequest, modelMap);
return "order/confirm";
} else {
OrderNo orderNo = placeOrderService.placeOrder(orderRequest);
modelMap.addAttribute("orderNo", orderNo.getNumber());
return "order/orderComplete";
}
}
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;
public class OrderRequestValidator implements Validator {
@Override
public boolean supports(Class<?> aClass) {
return OrderRequest.class.isAssignableFrom(aClass);
}
@Override
public void validate(Object o, Errors errors) {
OrderRequest orderReq = (OrderRequest) o;
if (orderReq.getOrderProducts() == null || orderReq.getOrderProducts().isEmpty()) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "orderProducts", "required");
} else {
for (int i = 0 ; i < orderReq.getOrderProducts().size() ; i++ ) {
OrderProduct orderProduct = orderReq.getOrderProducts().get(i);
if (orderProduct.getProductId() == null || orderProduct.getProductId().trim().isEmpty()) {
errors.rejectValue("orderProducts["+i+"].productId", "required");
}
if (orderProduct.getQuantity() <= 0) {
errors.rejectValue("orderProducts["+i+"].quantity", "nonPositive");
}
}
}
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "shippingInfo.receiver.name", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "shippingInfo.receiver.phone", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "shippingInfo.address.zipCode", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "shippingInfo.address.address1", "required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "shippingInfo.address.address2", "required");
}
}
사용자 U가 기능 F를 실행할 수 있는지
확인하는 것이 권한 검사이므로 권한 검사 자체는 복잡한 개념이 아니다.@PreAuthorize()
와 같은 어노테이션으로 서비스 메서드에 대한 권한 검사를 할 수 있는 기능을 제공 한다.import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BlockMemberService {
private MemberRepository memberRepository;
@PreAuthorize("hasRole('ADMIN')")
@Transactional
public void block(String memberId) {
Member member = memberRepository.findById(new MemberId(memberId));
if (member == null) throw new NoMemberException();
member.block();
}
org.springframework.security.access.prepost
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public interface PreAuthorize
extends annotation.Annotation
Maven: org.springframework.security:spring-security-core:4.0.3.RELEASE
@Service
public class OrderListService {
private OrderViewDao orderViewDao;
public OrderListService(OrderViewDao orderViewDao) {
this.orderViewDao = orderViewDao;
}
public List<OrderView> getOrderList(String ordererId) {
return orderViewDao.selectByOrderer(ordererId);
}
}