10 Sai lầm thường gặp nhất của Spring Framework

11321

Spring được cho là một trong những framework Java phổ biến nhất, và cũng là một con thú mạnh mẽ để thuần hoá. Mặc dù khái niệm cơ bản của nó khá dễ nắm bắt, tuy nhiên để trở thành một nhà phát triển Spring chuyên nghiệp đòi hỏi rất nhiều thời gian và công sức.

Trong bài này, chúng tôi sẽ giới thiệu một số lỗi phổ biến trong Spring, đặc biệt hướng tới các ứng dụng web và Spring Boot. Như Spring Boot của trang web, Spring Boot mang lại một cái nhìn sâu sắc về cách các ứng dụng được build, vì vậy bài viết này sẽ cố gắng bắt chước xem và cung cấp một cái nhìn tổng quan về một số mẹo tốt trong việc phát triển Spring Boot trên ứng dụng web.

Trong trường hợp bạn chưa quen với Spring Boot nhưng vẫn muốn thử một số điều được đề cập, thì tôi đã tạo kho trên GitHub kèm theo bài viết này. Nếu bạn cảm thấy khó hiểu tại bất kỳ thời điểm nào trong bài viết, tôi khuyên bạn nên clone code về và chơi với code trên máy thực của bạn.

Sai lầm thường gặp # 1: Quá tập trung vào tiểu tiết

Chúng tôi nghĩ sai lầm phổ biến này vì hội chứng “Nếu tôi (hoặc chúng tôi) không phát minh ra nó, thì nó không có nhiều giá trị.” là khá phổ biến trong thế giới phát triển phần mềm. Các triệu chứng bao gồm thường xuyên viết lại các đoạn mã thường được sử dụng và dường như có rất nhiều nhà phát triển bị ảnh hưởng.

Trong khi hiểu được bên trong của một library cụ thể và thực hiện nó là một việc tốt và cần thiết (và cũng là một quá trình học tập tuyệt vời), tuy nhiên nó gây trở ngại cho sự phát triển của bạn như, một kỹ sư phần mềm liên tục giải quyết việc triển khai ở low-level một cách chi tiết. Có một lý do tại sao lại abstraction và các framework như Spring tồn tại, chính là vì giúp bạn khỏi các công việc thủ công lặp đi lặp lại và cho phép bạn tập trung vào các chi tiết high-level hơn như tên miền và logic kinh doanh của bạn.

Vì vậy, hãy nắm lấy các khái niệm abstraction và lần tiếp theo bạn phải đối mặt với một vấn đề cụ thể, hãy search nhanh trước và xác định một library giải quyết vấn đề đã được tích hợp trong Spring. Ngày nay, tìm một giải pháp phù hợp là điều hoàn toàn có thể. Như ví dụ về một library hữu ích, tôi sẽ sử dụng ghi chú trong Project Lombok làm các ví dụ cho phần còn lại của bài viết này. Lombok như là một thư viện Java giúp bạn đơn giản hoá việc viết mã trong lập trình Java, hy vọng bạn không gặp khó khăn trong việc tự làm quen với thư viện. Ví dụ: hãy kiểm tra xem “tiêu chuẩn Java bean” như thế nào với Lombok:

Như bạn có thể tưởng tượng, mã trên được biên dịch để:

Tuy nhiên, chú ý rằng bạn rất có thể sẽ phải cài đặt một plugin trong trường hợp bạn định sử dụng Lombok với IDE của bạn. Phiên bản plugin của IntelliJ IDEA có thể được tìm thấy ở đây.

Sai lầm thường gặp # 2: ‘Rò rỉ’ Dữ liệu

Expose cấu trúc nội bộ của bạn không bao giờ là một ý hay vì nó tạo sự bị động trong thiết kế service và do đó tạo nên thói quen code tồi. “Rò rỉ” internals được biểu hiện qua việc làm cho cấu trúc cơ sở dữ liệu có thể truy cập được từ các điểm API cuối nhất định. Ví dụ, giả sử POJO sau (“Plain Old Java Object”) đại diện cho một bảng trong cơ sở dữ liệu của bạn:

Giả sử có một endpoint cần truy cập dữ liệuTopTalentEntity. Vì nó có thể trả lại yêu cầu của TopTalentEntity, nên giải pháp linh hoạt hơn sẽ tạo ra một lớp mới để đại diện cho dữ liệu TopTalentEntity trên điểm cuối API:

Bằng cách đó, việc thay đổi cơ sở dữ liệu back-end của bạn sẽ không yêu cầu bất kỳ thay đổi bổ sung trong lớp service. Hãy xem điều gì sẽ xảy ra trong trường hợp thêm trường ‘password’ vào TopTalentEntity để lưu trữ mật khẩu của người dùng trong cơ sở dữ liệu,  không có kết nối như TopTalentData, thì việc quên thay đổi front-end của service sẽ vô tình lộ một số thông tin bí mật !

Sai lầm thường gặp # 3: Separation of Concern một cách ngu ngốc

Khi ứng dụng của bạn phát triển, cấu trúc code ngày càng trở nên quan trọng hơn bao giờ hết. Trớ trêu thay, hầu hết các nguyên tắc kỹ thuật bắt đầu bị phá vỡ ở scale, đặc biệt là trong những trường hợp mà không có nhiều sự quan tâm cho thiết kế cấu trúc ứng dụng. Một trong những sai lầm phổ biến nhất mà các nhà phát triển thường phải chịu thua trước việc đặt mối quan tâm về code, dù rằng rất dễ thực hiện!

Những gì thường phá vỡ separation of concerns chỉ có thể là ‘dumping’ chức năng mới vào các lớp hiện có. Tất nhiên, đây là một giải pháp ngắn hạn tuyệt vời (đối với người mới bắt đầu và không đánh máy nhiều) nhưng nó chắc chắn sẽ trở thành một vấn đề trong quá trình test, lưu trữ hoặc ở một quá trình nào đó. Xem xét controller sau đây, nó trả về giá trị TopTalentData từ repository của nó:

Lúc đầu, có vẻ như có điều gì đó sai với đoạn code này. Nó cung cấp một list TopTalentDatađang được lấy từ các instance TopTalentEntity . Tuy nhiên, hãy xem xét kỹ hơn, chúng ta có thể thấy rằng có một vài điều mà TopTalentController đang thực hiện tại đây; Cụ thể là các yêu cầu mapping đến một endpoint cụ thể, truy xuất dữ liệu từ repository, và các đối tượng được chuyển đổi từ TopTalentRepository sang một format khác. Một giải pháp “cleaner” sẽ tách những concern đó vào các lớp của. Nó có thể trông như thế này:

Một lợi thế bổ sung cho hệ thống phân cấp này là nó cho phép chúng ta xác định nơi function tồn tại bằng cách kiểm tra tên lớp. Hơn nữa, trong quá trình test, chúng ta có thể dễ dàng thay thế bất kỳ lớp nào với thực hiện bản mock-up nếu cần thiết.

Sai lầm thường gặp # 4: Không thống nhất và xử lý lỗi kém

Về chủ đề nhất quán thì không nhất thiết phải là Spring (hay Java), nhưng đây là một khía cạnh quan trọng cần cân nhắc khi làm việc trong các dự án Spring. Trong khi style code có thể là vấn đề để tranh luận (và thường là vấn đề thỏa thuận trong một nhóm hoặc trong toàn bộ công ty), việc có một chuẩn chung sẽ trợ giúp đạt được năng suất tuyệt vời. Điều này đặc biệt đúng với các đội nhiều người; Tính nhất quán cho phép xử lý mọi chuyện khi không có nhiều nguồn lực được chi cho việc nắm xử lý hoặc tạo sự lằng nhằng về responsibility của các lớp khác nhau.

Xem xét một dự án Spring với các file configuration, service và controller. Từ ngữ phù hợp trong việc đặt tên khiến việc tìm kiếm trở nên dễ dàng, nơi mà bất kỳ dev nào cũng có thể quản lý theo cách code của họ. Kết hợp với các hậu tố configuration vào các lớp configuration, hậu tố Service tới các services và hậu tố của controller đối với controller.

Liên quan mật thiết với chủ đề nhất quán, xử lý lỗi ở phía máy chủ cũng là một vấn đề quan trọng. Nếu bạn phải xử lý các response ngoại lệ từ một API được viết tệ, bạn sẽ hiểu tại sao, nó là nỗi đau khi phân tích cú pháp một cách hợp lệ, và thậm chí còn đớn đau hơn nữa khi xác định lý do những ngoại lệ đó lại xảy ra ở nơi đầu tiên.

Với tư cách là một nhà phát triển API, bạn muốn gom tất cả các endpoint của người dùng và chuyển chúng thành định dạng lỗi thông thường. Điều này thường có nghĩa là có loại code lỗi và mô tả chứ không phải là giải pháp cop-out của a, trả về một thông báo “500 Internal Server Error”, hoặc b chỉ cần dumping StackTrace cho người dùng (mà thực sự cần tránh bằng mọi giá vì nó làm lộ dữ liệu của bạn, thêm vào đó tạo khó khăn để xử lý ở phía máy khách).

Một ví dụ về định dạng response lỗi thông thường có thể là:

Một cái gì đó tương tự như vậy thường gặp trong hầu hết các API phổ biến và hoạt động tốt vì nó có thể được ghi nhận lại một cách dễ dàng và có hệ thống. Chuyển các ngoại lệ sang định dạng này có thể được thực hiện bằng cách cung cấp annotation @ExceptionHandler cho một phương thức (ví dụ về annotation nằm trong Sai lầm Thường gặp # 6).

Sai lầm thường gặp # 5: Xử lý không hợp lý với Multithreading

Bất kể có gặp phải trong các ứng dụng desktop hoặc web, Spring hay không Spring, đa luồng có thể là một vấn đề để crack. Các vấn đề xảy ra bởi thực thi song song của các chương trình rất khó để bắt được và đôi khi vô cùng khó khăn để debug, trong thực tế, do tính chất của vấn đề, một khi bạn nhận ra rằng bạn đang đối phó với một vấn đề thực hiện song song có lẽ bạn sẽ phải bỏ qua các chương trình debug hoàn toàn và thực hiện test code “bằng tay” cho đến khi bạn tìm ra nguồn gây ra lỗi. Thật không may, không giải pháp nào tốt hơn tồn tại để giải quyết các vấn đề như vậy; Tùy thuộc vào trường hợp cụ thể của bạn, bạn sẽ phải đánh giá tình hình và sau đó tấn công vấn đề từ điểm mà bạn cho là tốt nhất.

Hãy là những người đầu tiên đăng ký vé Early Bird từ 01/04 – 15/04 với giá ưu đãi chỉ còn 150k

Dĩ nhiên, bạn sẽ muốn tránh lỗi đa luồng. Một lần nữa, phương pháp tiếp cận “một phù hợp cho tất cả” không tồn tại để thực hiện việc này, nhưng dưới đây là một số cân nhắc thực tế để debug và ngăn chặn các lỗi đa luồng:

  • Tránh Global State

Thứ nhất, luôn luôn ghi nhớ vấn đề “global state”. Nếu bạn đang tạo ứng dụng đa luồng, hay chắc chắn có thể điều chỉnh được, mọi thứ trên global cần được theo dõi chặt chẽ và nếu có thể, hãy loại bỏ hoàn toàn. Nếu có một lý do tại sao biến global  phải được sửa đổi, hãy cẩn thận sử dụng đồng bộ hóa và theo dõi hiệu suất của ứng dụng để xác nhận rằng nó không chạy chậm do thời gian chờ đợi period.

  • Tránh Mutability

Điều này đến thẳng từ lập trình chức năng và sửa đổi với OOP, cho biết rằng sự thay đổi lớp và thay đổi state nên tránh. Nói tóm lại, như đề cập trên, các phương thức setter và có các trường cuối cùng riêng trên tất cả các mô hình của bạn. Lần duy nhất giá trị của chúng bị đột biến là trong quá trình construct. Bằng cách này bạn có thể chắc chắn rằng không có vấn đề xung đột phát sinh và truy cập vào các thuộc tính đối tượng sẽ cung cấp các giá trị chính xác ở tất cả các lần.

  • Log dữ liệu quan trọng

Đánh giá nơi ứng dụng của bạn có thể xảy ra rắc rối và log lại tất cả các dữ liệu quan trọng. Nếu xảy ra lỗi, bạn sẽ thấy tuyệt vời khi có thông tin xác định những request nào đã nhận được và hiểu rõ hơn về lý do tại sao ứng dụng của bạn không hoạt động. Cần lưu ý rằng log cho ra thêm file I/O và do đó không nên lạm dụng vì nó có thể ảnh hưởng nghiêm trọng đến hiệu suất của ứng dụng.

  • Tái sử dụng Implementation hiện có

Bất cứ khi nào bạn cần tạo ra các thread của riêng mình (ví dụ để tạo request async đến các dịch vụ khác nhau), hãy sử dụng lại các implementation an toàn hiện có thay vì tạo các giải pháp cho riêng bạn. Điều này, phần lớn có nghĩa là sử dụng ExecutorServices và các tính năng CompletablesFutures theo functional-style rõ ràng của Java 8 để tạo thread. Spring cũng cho phép xử lý request async thông qua lớp DeferredResult.

Sai lầm thường gặp # 6: Không sử dụng validation dựa trên annotation

Hãy tưởng tượng service TopTalent của chúng tôi từ trước đã yêu cầu một endpoint để thêm các Top Talents. Và hơn nữa, giả sử rằng có điều kiện validation, mỗi tên mới cần phải có độ dài chính xác 10 ký tự. Thì cách để thực hiện việc này có thể như sau:

Tuy nhiên, ở trên (ngoài việc construct kém) không thực sự là một giải pháp hoàn hảo. Chúng tôi đang kiểm tra nhiều loại validation (cụ thể là TopTalentData không được null, và TopTalentData.name không được null, và TopTalentData.name có độ dài 10 ký tự), cũng như trả exception nếu dữ liệu không hợp lệ.

Việc này có thể được thực hiện tốt hơn bằng cách sử dụng validator Hibernate với Spring. Đầu tiên hãy refactor phương thức addTopTalent để hỗ trợ validation:

Ngoài ra, chúng ta sẽ phải chỉ ra thuộc tính nào chúng ta muốn xác nhận trong lớp TopTalentData :

Bây giờ Spring sẽ chặn request và validate trước khi phương thức được gọi – không cần thêm các bước test thủ công.

Một cách khác chúng ta có thể đạt được cùng một kết quả là bằng cách tạo các annotation riêng của chúng tôi. Mặc dù bạn thường sẽ chỉ sử dụng annotation tùy chỉnh khi nhu cầu của bạn vượt quá cài đặt mặc định của Hibernate. Ví dụ, giả vờ rằng @Length không tồn tại. Bạn sẽ tạo một validator để kiểm tra độ dài chuỗi bằng cách tạo hai lớp bổ sung, một cho việc xác nhận validation và một thuộc tính cho annotation:

Lưu ý rằng trong những trường hợp này, các phương pháp hay nhất về tách các mối quan tâm yêu cầu bạn phải đánh dấu một thuộc tính là hợp lệ nếu nó là null (s == null trong phương thức isValid), và sau đó sử dụng annotation @NotNull nếu đó là một yêu cầu bổ sung cho thuộc tính:

Sai lầm thường gặp # 7: (Vẫn còn) Sử dụng Configuration dựa trên XML

Mặc dù XML là điều cần thiết cho các phiên bản trước của Spring, hiện nay phần lớn configuration có thể được thực hiện chỉ cần thông qua code Java / annotation; Các configuration XML chỉ là một dạng code boilerplate bổ sung và không cần thiết.

Bài viết này (cũng như kho lưu trữ GitHub kèm theo) sử dụng các annotation để cấu hình Spring và Spring biết được beans nào nên được dùng vì root đã được chú thích bằng một annotation phức hợp @SpringBootApplication , như sau:

Annotation tổng hợp (bạn có thể tìm hiểu thêm về nó trong tài liệu Spring, chỉ đơn giản cho Spring một gợi ý để quét các package để lấy beans) Trong trường hợp cụ thể của chúng ta, điều này có nghĩa là package phần mềm dưới đây (co.kukurin) sẽ được sử dụng cho wiring:

  • @Component (TopTalentConverter, MyAnnotationValidator)
  • @RestController (TopTalentController)
  • @Repository (TopTalentRepository)
  • Lớp @Service (TopTalentService@Service

Nếu chúng ta có thêm bất kỳ lớp annotation @Configuration nào, chúng cũng sẽ được kiểm tra configuration dựa trên Java.

Hãy là những người đầu tiên đăng ký vé Early Bird từ 01/04 – 15/04 với giá ưu đãi chỉ còn 150k

Sai lầm thường gặp # 8: Quên các Profile

Một vấn đề hay gặp phải trong phát triển máy chủ là phân biệt giữa các kiểu configuration khác nhau, thường là các configuration sản xuất và phát triển của bạn. Thay vì thay thế thủ công các mục configuration khác nhau mỗi khi bạn chuyển từ test sang deploy ứng dụng của mình, thì cách hiệu quả hơn là sử dụng profile:

application.yaml file

application-dev.yaml file

Có lẽ bạn sẽ không muốn vô tình thực hiện bất kỳ hành động nào trên cơ sở dữ liệu sản xuất của mình trong khi điều chỉnh code, vì vậy nó có sense để thiết lập cấu hình mặc định cho dev. Trên máy chủ, bạn có thể tự ghi đè lên configuration bằng cách cung cấp tham số -Dspring.profiles.active=prod cho JVM. Ngoài ra, bạn cũng có thể đặt biến environment của hệ điều hành cho profile mặc định mong muốn.

Sai lầm thường gặp # 9: Thất bại trong việc nắm bắt Dependency Injection

Sử dụng Dependency Injection đúng cách với Spring có nghĩa là cho phép nó wire tất cả các đối tượng của bạn với nhau bằng cách scan tất cả các lớp configuration mong muốn. Điều này chứng tỏ hữu ích của việc tách quan hệ và làm cho việc test toàn bộ dễ dàng hơn. Thay vì kết hợp các lớp chặt chẽ bằng cách làm như thế này:

Chúng tôi cho phép Spring làm wire cho chúng tôi:

Marek Hevery từ Google giải thích các lý do Dependency Injection rất phức tạp, thế nên hãy xem nó được sử dụng như thế nào trong thực tế. Trong phần phân chia concerns (Những sai lầm thường gặp # 3), chúng tôi tạo ra một lớp service và controller. Giả sử chúng ta muốn kiểm tra controller theo assumption rằng TopTalentService hoạt động chính xác. Chúng ta có thể chèn một đối tượng mô phỏng thay cho implementation service thực tế bằng cách cung cấp một lớp configuration riêng:

Sau đó, chúng ta có thể inject các mock object bằng cách cho Spring sử dụng SampleUnitTestConfig cung cấp configuration:

Điều này cho phép chúng ta sử dụng configuration context để chèn beans vào unit test.

Sai lầm thường gặp # 10: Không test hoặc test không đúng

Mặc dù suy nghĩ unit test đã tồn tại với chúng ta trong một thời gian dài, nhugn7 nhiều developer có vẻ như “quên” làm điều này (đặc biệt là nếu không bắt buộc) hoặc chỉ làm thêm cho có. Điều này rõ ràng không đúng vì các bài test không chỉ nên test tính hợp lý của code, mà còn là tài liệu hướng dẫn app sẽ hoạt động trong các tình huống khác nhau như thế nào.

Khi test các web service, bạn hiếm khi làm các unit test vì giao tiếp qua HTTP thường đòi hỏi bạn phải gọi DispatcherServlet của Spring và xem điều gì sẽ xảy ra khi nhận được HttpServletRequest thực sự (giúp trở thành một bài test tích hợp, xử lý validation, serialization , Vv). REST Assured, một DSL của Java để test dễ dàng các REST service, trên top của MockMVC, đã chứng minh đây là một giải pháp rất mượt. Xem xét đoạn code sau với Dependency Injection:

SampleUnitTestConfig kết nối việc implementation mô hình TopTalentService vào TopTalentController trong khi tất cả các lớp khác đều có wire bằng cách sử dụng cấu hình chuẩn được suy ra từ các scan package bắt nguồn từ package lớp ứng dụng. RestAssuredMockMvcchỉ đơn giản được sử dụng để thiết lập một environment nhỏ và gửi một request GET đến /toptal/get endpoint.

Hãy là những người đầu tiên đăng ký vé Early Bird từ 01/04 – 15/04 với giá ưu đãi chỉ còn 150k

Trở thành Master của Spring

Spring là một framework mạnh mẽ dễ bắt đầu, nhưng đòi hỏi công sức và thời gian để có thể đạt cảnh giới Master. Dành thời gian để làm quen với framework chắc chắn sẽ cải thiện năng suất của bạn trong thời gian dài và cuối cùng là giúp bạn viết code gọn hơn và trở thành lập trình viên tốt hơn.

Techtalk via Toptal

CHIA SẺ