最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java項目中Service 層不直接返回Result 對象的原因分析

 更新時間:2026年02月27日 15:00:45   作者:一只叫煤球的貓  
在Service層直接返回Result對象會導(dǎo)致業(yè)務(wù)邏輯與表現(xiàn)邏輯耦合,降低代碼清晰度和可維護性,正確的做法是讓每一層專注于自己的職責(zé),保持代碼可復(fù)用性,這篇文章給大家介紹為什么Java里面,Service 層不直接返回Result對象,感興趣的朋友跟隨小編一起看看吧

前言

昨天在Code Review時,我發(fā)現(xiàn)阿城在Service層直接返回了Result對象。

指出這個問題后,阿城有些不解,反問我為什么不能這樣寫。

于是我們展開了一場技術(shù)討論(battle ??)。

討論過程中,我發(fā)現(xiàn)這個看似簡單的設(shè)計問題,背后其實涉及分層架構(gòu)、職責(zé)劃分、代碼復(fù)用等多個重要概念。

與其讓這次討論的內(nèi)容隨風(fēng)而去,不如整理成文,幫助更多遇到同樣困惑的朋友理解原因。

知其然,更知其所以然。

耐心看完,你一定有所收獲。

正文

職責(zé)分離原則

在傳統(tǒng)的MVC架構(gòu)中,Service層和Controller層各自承擔(dān)著不同的職責(zé)。

Service層負責(zé)業(yè)務(wù)邏輯的處理,而Controller層負責(zé)HTTP請求的處理和響應(yīng)格式的封裝。

當(dāng)我們將數(shù)據(jù)包裝成 Result 對象的任務(wù)交給 Service 層時,意味著 Service 層不再單純地處理業(yè)務(wù)邏輯,而是牽涉到了數(shù)據(jù)處理和響應(yīng)的部分。

這樣會導(dǎo)致業(yè)務(wù)邏輯與表現(xiàn)邏輯的耦合,降低了代碼的清晰度和可維護性。

看一個不推薦的寫法:

@Service
public class UserService {
    public Result<User> getUserById(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            return Result.error(404, 用戶不存在);
        }
        return Result.success(user);
    }
}
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/user/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }
}

上面代碼中,Service 層不僅負責(zé)從數(shù)據(jù)庫獲取用戶信息,還直接處理了返回的結(jié)果。

如果我們需要改變返回的格式,或者進行錯誤信息的標準化,所有 Service 層的方法都需要修改。這樣會導(dǎo)致代碼的高耦合。

相比之下,以下做法將展示邏輯留給 Controller 層,保證了業(yè)務(wù)邏輯的純粹性:

@Service
public class UserService {
    public User getUserById(Long id) {
        User user = userMapper.selectById(id);
        if (user == null) {
            throw new BusinessException(用戶不存在);
        }
        return user;
    }
}
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/user/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return Result.success(user);
    }
}

讓每一層都專注于自己的職責(zé)。

可復(fù)用性問題

當(dāng)Service層返回Result時,會嚴重影響方法的可復(fù)用性。

假設(shè)我們有一個訂單服務(wù)需要調(diào)用用戶服務(wù):

@Service
public class OrderService {
    @Autowired
    private UserService userService;
    public void createOrder(Long userId, OrderDTO orderDTO) {
        // 不推薦的方式:需要解包Result
        Result<User> userResult = userService.getUserById(userId);
        if (!userResult.isSuccess()) {
            throw new BusinessException(userResult.getMessage());
        }
        User user = userResult.getData();
        // 后續(xù)業(yè)務(wù)邏輯
        validateUserStatus(user);
        // ...
    }
}

這種寫法有個很明顯的問題。

OrderService 作為另一個業(yè)務(wù)服務(wù),業(yè)務(wù)之間的調(diào)用本來應(yīng)該簡單直接,但使用 Result 帶來了兩個問題:

  • 不知道 Result 里到底包含什么,還得去查看代碼里面的實現(xiàn),寫起來麻煩。
  • 還需要額外判斷 Result 的狀態(tài),增加了不必要的復(fù)雜度。

如果是調(diào)用第三方外部服務(wù),需要這種包裝還能理解,但在自己業(yè)務(wù)之間互相調(diào)用時,完全沒必要這樣做。

如果Service返回純業(yè)務(wù)對象:

@Service
public class OrderService {
    @Autowired
    private UserService userService;
    public void createOrder(Long userId, OrderDTO orderDTO) {
        // 推薦的方式:直接獲取業(yè)務(wù)對象
        User user = userService.getUserById(userId);
        // 后續(xù)業(yè)務(wù)邏輯
        validateUserStatus(user);
        // ...
    }
}

代碼變得簡潔且符合直覺。

業(yè)務(wù)層之間直接傳遞業(yè)務(wù)對象,保持簡單和清晰。

異常處理機制

有些 Service 層在業(yè)務(wù)判斷失敗后,會直接返回 Result.fail(xxx) 這樣的代碼,例如:

public Result<Void> createOrder(Long userId, OrderDTO orderDTO) {
    if (userId == null) {
        return Result.fail("用戶ID不能為空");
    }
    // 后續(xù)業(yè)務(wù)邏輯
    return Result.success();
}

這種做法有幾個問題:

  • 重復(fù)的錯誤處理:每個方法都得寫一大堆類似的錯誤判斷代碼,增加了代碼量。
  • 錯誤分散:錯誤處理分散在每個方法里,如果需要改進錯誤邏輯,要在多個地方修改,麻煩且容易出錯。

而如果我們通過拋出異常并結(jié)合全局異常處理來統(tǒng)一處理錯誤,例如:

public void createOrder(Long userId, OrderDTO orderDTO) {
    if (userId == null) {
        throw new BusinessException("用戶ID不能為空");
    }
    // 后續(xù)業(yè)務(wù)邏輯
}

再通過全局異常捕獲來轉(zhuǎn)換為 Result

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException e) {
        return Result.error(400, e.getMessage());
    }
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e) {
        log.error("系統(tǒng)異常", e);  // 這里可以查看堆棧信息
        return Result.error(500, "系統(tǒng)繁忙");
    }
}

這樣做的好處是:

  • 減少重復(fù)代碼:業(yè)務(wù)方法不再需要寫重復(fù)的錯誤判斷,代碼更簡潔。
  • 集中錯誤處理:錯誤處理集中在一個地方,修改時只需修改全局異常處理器,不用改動每個 Service 層方法。
  • 業(yè)務(wù)與錯誤分離:業(yè)務(wù)邏輯專注處理核心功能,錯誤處理交給統(tǒng)一的機制,代碼更加清晰易懂。

而且異??梢詳y帶更豐富的上下文信息,如果業(yè)務(wù)側(cè)需要時,可以帶上堆棧信息,便于一些問題的定位。

測試便利性

Service層返回業(yè)務(wù)對象而不是Result時,能夠大大提升單元測試的便利性:

@SpringBootTest
public class UserServiceTest {
    @Autowired
    private UserService userService;
    @Test
    public void testGetUserById() {
        // 推薦的方式:直接斷言業(yè)務(wù)對象
        User user = userService.getUserById(1L);
        assertNotNull(user);
        assertEquals(張三, user.getName());
    }
    @Test
    public void testGetUserById_NotFound() {
        // 推薦的方式:斷言拋出異常
        assertThrows(BusinessException.class, () -> {
            userService.getUserById(999L);
        });
    }
}

如果Service返回Result,測試代碼則需要寫得更復(fù)雜:

@Test
public void testGetUserById() {
    // 不推薦的方式:需要解包Result
    Result<User> result = userService.getUserById(1L);
    assertTrue(result.isSuccess());
    assertNotNull(result.getData());
    assertEquals(張三, result.getData().getName());
}

測試代碼變得莫名冗長,還得去關(guān)注響應(yīng)結(jié)構(gòu),這并不是Service層測試的關(guān)注點。

Service 層本應(yīng)專注于業(yè)務(wù)邏輯,測試也應(yīng)該直接驗證業(yè)務(wù)數(shù)據(jù)。

領(lǐng)域驅(qū)動設(shè)計角度

再換個角度。

從領(lǐng)域驅(qū)動設(shè)計(DDD)的角度來看,Service 層屬于應(yīng)用層或領(lǐng)域?qū)?,?yīng)該使用領(lǐng)域語言來表達業(yè)務(wù)邏輯。

Result 是基礎(chǔ)設(shè)施層的概念,代表 HTTP 響應(yīng)格式,不應(yīng)該污染領(lǐng)域?qū)印?/p>

例如,考慮轉(zhuǎn)賬業(yè)務(wù):

@Service
public class TransferService {
    public TransferResult transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
        Account fromAccount = accountRepository.findById(fromAccountId);
        Account toAccount = accountRepository.findById(toAccountId);
        fromAccount.deduct(amount);
        toAccount.deposit(amount);
        accountRepository.save(fromAccount);
        accountRepository.save(toAccount);
        return new TransferResult(fromAccount, toAccount, amount);
    }
}

在這個例子中,TransferResult 是一個領(lǐng)域?qū)ο?,代表了轉(zhuǎn)賬的結(jié)果,包含了與業(yè)務(wù)相關(guān)的意義,而不是一個通用的 HTTP 響應(yīng)封裝 Result

這種做法更符合領(lǐng)域模型的表達,體現(xiàn)了領(lǐng)域?qū)拥穆氊?zé)——處理業(yè)務(wù)邏輯,而不是涉及 HTTP 響應(yīng)格式的細節(jié)。

接口適配的靈活性

當(dāng) Service 層返回純粹的業(yè)務(wù)對象時,Controller 層可以根據(jù)不同的接口需求靈活封裝響應(yīng):

@RestController
@RequestMapping("/api")
public class UserController {
    @Autowired
    private UserService userService;
    // REST接口返回Result
    @GetMapping("/user/{id}")
    public Result<User> getUser(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return Result.success(user);
    }
    // GraphQL接口直接返回對象
    @QueryMapping
    public User user(@Argument Long id) {
        return userService.getUserById(id);
    }
    // RPC接口返回自定義格式
    @DubboService
    public class UserRpcServiceImpl implements UserRpcService {
        public UserDTO getUserById(Long id) {
            User user = userService.getUserById(id);
            return convertToDTO(user);
        }
    }
}

同一個Service方法可以被不同類型的接口復(fù)用,每個接口根據(jù)自己的協(xié)議要求封裝響應(yīng)。

強行使用 Result 會導(dǎo)致接口的適配性變差,無法根據(jù)不同協(xié)議的需求靈活定制響應(yīng)格式。

靈活性反而丟失了。

事務(wù)邊界清晰

Service 層通常是事務(wù)邊界所在,當(dāng) Service 返回業(yè)務(wù)對象時,事務(wù)的語義更加清晰:

@Service
public class OrderService {
    @Transactional
    public Order createOrder(OrderDTO orderDTO) {
        Order order = new Order();
        // 設(shè)置訂單屬性
        orderMapper.insert(order);
        // 扣減庫存
        inventoryService.deduct(orderDTO.getProductId(), orderDTO.getQuantity());
        return order;
    }
}

在這個例子中,事務(wù)是圍繞 Service 層的方法展開的,@Transactional 注解確保在業(yè)務(wù)邏輯執(zhí)行失敗時,事務(wù)會回滾。因為方法正常返回時,事務(wù)會提交;如果拋出異常,事務(wù)會回滾,事務(wù)的邊界非常明確。

如果 Service 返回的是 Result,很難界定事務(wù)是否應(yīng)該回滾。比如:

public Result<Order> createOrder(OrderDTO orderDTO) {
    Order order = new Order();
    // 設(shè)置訂單屬性
    orderMapper.insert(order);
    // 扣減庫存
    Result<Void> inventoryResult = inventoryService.deduct(orderDTO.getProductId(), orderDTO.getQuantity());
    if (!inventoryResult.isSuccess()) {
        return Result.fail("庫存不足");
    }
    return Result.success(order);
}

在這種情況下,如果庫存不足,雖然 Result 返回失敗信息,但事務(wù)并不會回滾,可能會導(dǎo)致數(shù)據(jù)不一致,反而還得額外去拋出異常。

而通過拋出異常的方式,事務(wù)的回滾語義非常清晰:異常拋出則回滾,方法正常返回則提交,這種設(shè)計確保了事務(wù)的邊界更加明確,避免了潛在的數(shù)據(jù)一致性問題。

寫在最后

到此這篇關(guān)于Java項目中Service 層不直接返回Result 對象的原因分析的文章就介紹到這了,更多相關(guān)java service 層不直接返回result 對象內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 簡單了解SpringBoot過濾器及使用方式

    簡單了解SpringBoot過濾器及使用方式

    這篇文章主要介紹了簡單了解SpringBoot過濾器及使用方式,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-04-04
  • java?Date和SimpleDateFormat時間類詳解

    java?Date和SimpleDateFormat時間類詳解

    這篇文章主要介紹了java?Date和SimpleDateFormat時間類詳解,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • 用Java實現(xiàn)希爾排序的示例

    用Java實現(xiàn)希爾排序的示例

    問題:現(xiàn)有一段程序S,可以對任意n個數(shù)進行排序。如果現(xiàn)在需要對n^2個數(shù)進行排序,最少需要調(diào)用S多少次?只允許調(diào)用S,不可以做別的操作。我們用希爾排序來做解決這個
    2013-11-11
  • Java實戰(zhàn)角色權(quán)限后臺腳手架系統(tǒng)的實現(xiàn)流程

    Java實戰(zhàn)角色權(quán)限后臺腳手架系統(tǒng)的實現(xiàn)流程

    只學(xué)書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+Springboot+Maven+myBaits-Plus+Vue+Element-UI+Mysql實現(xiàn)一個角色權(quán)限后臺腳手架系統(tǒng),大家可以在過程中查缺補漏,提升水平
    2022-01-01
  • Java實現(xiàn)簡單雙色球搖獎功能過程解析

    Java實現(xiàn)簡單雙色球搖獎功能過程解析

    這篇文章主要介紹了Java實現(xiàn)簡單雙色球搖獎功能過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-09-09
  • 使用JAVA8 filter對List多條件篩選的實現(xiàn)

    使用JAVA8 filter對List多條件篩選的實現(xiàn)

    這篇文章主要介紹了使用JAVA8 filter對List多條件篩選的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-03-03
  • idea新建springboot項目的方法

    idea新建springboot項目的方法

    這篇文章主要介紹了idea新建springboot項目的方法,文中講解非常細致,圖文并茂幫助大家更好的理解學(xué)習(xí),感興趣的朋友可以了解下
    2020-06-06
  • Java判斷對象是否為空的四種方法小結(jié)

    Java判斷對象是否為空的四種方法小結(jié)

    這篇文章主要介紹了Java判斷對象是否為空的四種方法,判斷對象是否為空有多種方法,包括使用==或!=運算符直接比較對象與null,使用Objects.isNull()方法,以及用instanceof運算符或Optional類進行更安全的空值處理,需要的朋友可以參考下
    2024-10-10
  • Java設(shè)置PDF跨頁表格重復(fù)顯示表頭行的步驟詳解

    Java設(shè)置PDF跨頁表格重復(fù)顯示表頭行的步驟詳解

    這篇文章主要給大家介紹了關(guān)于Java設(shè)置PDF跨頁表格重復(fù)顯示表頭行的相關(guān)資料,這里使用的是Free Spire.PDF for Java的jar包,Spire.PDF for Java 是一款專門對 PDF 文檔進行操作的 Java 類庫,需要的朋友可以參考下
    2021-07-07
  • Java?restTemplate發(fā)送get請求query參數(shù)傳遞問題解決

    Java?restTemplate發(fā)送get請求query參數(shù)傳遞問題解決

    這篇文章主要為大家介紹了Java?restTemplate發(fā)送get請求query參數(shù)傳遞問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2023-11-11

最新評論

兴业县| 崇州市| 富平县| 即墨市| 屏东县| 外汇| 北票市| 山丹县| 吴堡县| 玉屏| 龙口市| 化德县| 即墨市| 富平县| 阜宁县| 莒南县| 凤城市| 凉山| 岳普湖县| 剑川县| 华蓥市| 和政县| 疏勒县| 吉安市| 平阴县| 泽普县| 德化县| 盐边县| 罗山县| 南陵县| 淮北市| 株洲市| 东阳市| 盘山县| 邛崃市| 宁强县| 中宁县| 浮梁县| 甘孜县| 灵宝市| 寿阳县|