Chia sẻ kiến thức lập trình

S.O.L.I.D – Single Responsibility

Mỗi người 1 việc

Trong doanh nghiệp của chúng ta, có rất nhiều bộ phận, mỗi người lại có những nhiệm vụ và chức năng khác nhau, nếu như một người phải ôm đồm quá nhiều việc dần dần họ sẽ bị quá tải sinh ra tâm lý chán nản và nghỉ việc. Source code của chúng ta cũng vậy, mỗi hàm chỉ nên thực hiện 1 nhiệm vụ, và 1 lớp chỉ nên có các hàm liên quan mật thiết với nhau, nếu hàm hay 1 lớp làm quá nhiều việc nó sẽ trở nên quá tải cho #IDE, quá tải cho người tiếp nhận source code và khi phải cập nhật thêm tính năng mới chắc hẳn chúng ta sẽ run tay và lỗi xảy ra cực kì nghiêm trọng

Ví dụ thực tế

Chúng ta hãy nhìn vào lớp controller này nhé

class AuthController {
    public ResponseEntity getUserProfile(String username, String token) {
        Authentication authentication = authService.authenticateUser(token)
                                                   .orElseThrow(() -> new UserAuthException("Couldn't found user [" + username + "]"));
        User user = userService.getUser(username);
        return ResponseEntity.ok(user);
    }
    public ResponseEntity authenticateUser(
        LoginRequest loginRequest
    ) {
        Authentication authentication = authService.authenticateUser(loginRequest)
                                                   .orElseThrow(() -> new UserLoginException("Couldn't login user [" + loginRequest + "]"));
        CustomUserDetails customUserDetails = (CustomUserDetails) authentication.getPrincipal();
        logger.info("Logged in User returned [API]: " + customUserDetails.getUsername());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        return authService.createAndPersistRefreshTokenForDevice(authentication, loginRequest)
                          .map(RefreshToken::getToken)
                          .map(refreshToken -> {
                              String jwtToken = authService.generateToken(customUserDetails);
                              return ResponseEntity.ok(new JwtAuthenticationResponse(jwtToken, refreshToken, tokenProvider.getExpiryDuration()));
                          })
                          .orElseThrow(() -> new UserLoginException("Couldn't create refresh token for: [" + loginRequest + "]"));
    }
}

Có thể chưa hiểu nghiệp vụ là gì nhưng chúng ta cũng có thể thấy đây là một lớp xử lý rất phức tạp, hãy phân tích nó 1 chút nhé. Lớp này chuyên để xử lý việc xác thực và nó đang có 2 hàm:

  1. getUserProfile: lấy thông tin của user
  2. authenticateUser: xác thực user

Ồ như vậy có đúng không nhỉ? 1 lớp chuyên xác thực mà lại đi lấy thông tin của user? chúng ta không nên làm như vậy
Tiếp theo chúng ta có thể thấy lớp controller này xử lý các exception rất rối, và nếu controller nào cũng phải xử lý thế này thì source code sẽ dần dần không thể đọc nổi

Giải quyết vấn đề

Vậy việc chúng ta cần làm đó là tạo ra các lớp, mỗi lớp sẽ thực hiện các công việc khác nhau

class UserService {
    public User getUserProfile(String username) {
        return userRepo.findByUsername(username)
            .orElseThrow(() -> new NotFoundException("user: " + username));
    }
}
class UserController {
    public User getUserProfile(String username) {
        return userService.getUserProfile(username);
    }
}
class AuthService {
    public JwtToken authen(String username, String password) {
        return credentialRepo.getCredential(username, password)
            .map(Credential::getUsername)
            .map(this::createAndPersistRefreshTokenForDevice)
            .orElseThrow(() -> new AuthException("Couldn't create refresh token for: [" + username + "]"));
    }
    private JwtToken createAndPersistRefreshTokenForDevice(String username) {
        return refreshTokenProvider.create(username)
            .map(RefreshToken::getToken)
            .map(refreshToken -> {
                String jwtToken = authService.generateToken(customUserDetails);
                return new JwtToken(jwtToken, refreshToken, tokenProvider.getExpiryDuration()));
            });
    }
}
class AuthController {
    public JwtToken authenticateUser(LoginRequest request) {
        return authService.authen(request.getUsername(), request.getPassword());
    }
}
  1. UserService: lớp này sẽ chịu trách nhiệm gọi xuống tầng Repo (tầng truy xuất cơ sở dữ liệu) và xử lý logic cho dữ liệu lấy được
  2. UserController: lớp này sẽ thực sự đơn giản là forward dữ liệu xuống lớp UserService theo đúng tư tưởng của mô hình #MVC
  3. AuthService: lớp này cũng chịu trách nhiệm gọi xuống tầng Repo và xử lý logic, tuy nhiên logic của lớp này khá phức tạp nên cần tách ra thành 2 hàm riêng, 1 hàm để xác thực username password và 1 hàm để tạo ra jwt token
  4. AuthController: tương tự như UserController lớp này cũng chỉ forward dữ liệu xuống tầng Service mà thôi

Tổng kết

Bạn có thể thấy, source code đã trở nên sạch sẽ và nhẹ nhàng hơn rất nhiều. Như vậy có thể thấy Single-responsibility principle sẽ giúp chúng ta:

  1. Tách source code ra thành các file nhỏ hơn
  2. Tăng khả năng common source code
  3. Làm cho source code trở nên rõ ràng dễ đọc hiểu và dễ maintain hơn
  4. Thu nhỏ phạm vi bị ảnh hưởng khi phải nâng cấp source code sau này

Nguyên tắc này mình đang áp dụng triệt để, các bạn cũng vậy nhé, 🙂

Share: