”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > Saga 模式如何解决分布式事务问题:方法和实际示例

Saga 模式如何解决分布式事务问题:方法和实际示例

发布于2024-11-01
浏览:568

1. Understanding the Problem: The Complexity of Distributed Transactions

How the Saga Pattern Resolves Distributed Transaction Issues: Methods and Real-World Example

Distributed transactions involve multiple microservices, where each service performs a part of a transaction. For instance, an e-commerce platform might involve services like payment, inventory, and order management. These services need to work together to complete a transaction. However, what happens if one of these services fails?

1.1 A Real-World Scenario

Imagine an e-commerce application where the following steps occur during an order placement:

  • Step 1 : Deduct payment from the customer’s account.
  • Step 2 : Reduce the item count in the inventory.
  • Step 3 : Create an order in the order management system.

How the Saga Pattern Resolves Distributed Transaction Issues: Methods and Real-World Example

If the inventory service fails after the payment is deducted but before the order is created, the system ends up in an inconsistent state. The customer is charged, but no order is placed.

1.2 Traditional Solutions and Their Limitations

To handle such failures, one might consider using a distributed transaction with a two-phase commit protocol. However, this introduces several issues:

  • High Latency : Each service must lock resources during the transaction, leading to increased latency.
  • Reduced Availability : If any service fails, the entire transaction is rolled back, reducing the overall system availability.
  • Tight Coupling : Services become tightly coupled, making it harder to scale or modify individual services.

2. How the Saga Pattern Solves the Problem

In distributed systems, transactions often span multiple microservices. Ensuring that all services either complete successfully or none at all is challenging. The traditional way of handling this—using distributed transactions with two-phase commit—can be problematic due to issues like high latency, tight coupling, and reduced availability.

How the Saga Pattern Resolves Distributed Transaction Issues: Methods and Real-World Example

The Saga pattern offers a more flexible approach. Instead of attempting to execute a transaction as a single unit, the Saga pattern breaks down the transaction into smaller, isolated steps that can be performed independently. Each step is a local transaction that updates the database and then triggers the next step. If a step fails, the system performs compensating actions to undo the changes made by previous steps, ensuring that the system can return to a consistent state.

2.1 What is the Saga Pattern?

The Saga pattern is essentially a sequence of smaller transactions that are executed one after the other. Here’s how it works:

  • Local Transactions : Each service involved in the transaction performs its own local transaction. For instance, in an order processing system, one service might handle payment, another inventory, and yet another the order record.
  • Event or Message Publishing : After a service completes its local transaction, it publishes an event or sends a message indicating the successful completion of that step. For example, after the payment is processed, the payment service might publish a "PaymentCompleted" event.
  • Triggering the Next Step : The next service in the sequence listens for the event and, upon receiving it, proceeds with its local transaction. This continues until all steps in the transaction are completed.
  • Compensating Actions : If any step fails, compensating actions are invoked. These actions are designed to reverse the changes made by the previous steps. For instance, if the inventory reduction fails after payment, a compensating action would refund the payment.

2.2 Types of Sagas

There are two main ways to implement the Saga pattern: Choreography and Orchestration.

2.2.1 Choreography Saga

In a Choreography Saga, there is no central coordinator. Instead, each service involved in the Saga listens for events and decides when to act based on the outcome of previous steps. This approach is decentralized and allows services to operate independently. Here’s how it works:

  • Event-Based Coordination : Each service is responsible for handling the events that are relevant to it. For example, after the payment service processes a payment, it emits a "PaymentCompleted" event. The inventory service listens for this event and, when it receives it, deducts the item count.
  • Decentralized Control : Since there is no central coordinator, each service must know what to do next based on the events it receives. This gives the system more flexibility but requires careful planning to ensure that all services understand the correct sequence of operations.
  • Compensating Actions : If a service detects that something went wrong, it can emit a failure event, which other services listen for to trigger compensating actions. For example, if the inventory service cannot update the inventory, it might emit an "InventoryUpdateFailed" event, which the payment service listens for to trigger a refund.

Advantages of Choreography:

  • Loose Coupling : Services are loosely coupled, which makes it easier to scale and modify individual services.
  • Resilience : Since each service acts independently, the system can be more resilient to failures in individual services.

Challenges of Choreography:

  • Complexity : As the number of services grows, managing and understanding the flow of events can become complex.
  • Lack of Central Control : Without a central coordinator, it can be harder to monitor and debug the overall transaction flow.

2.2.2 Orchestration Saga

In an Orchestration Saga, a central orchestrator controls the flow of the transaction. The orchestrator determines the sequence of steps and handles the communication between services. Here’s how it works:

  • Centralized Control : The orchestrator sends commands to each service in sequence. For example, the orchestrator might first instruct the payment service to process a payment. Once that’s done, it tells the inventory service to update the inventory, and so on.
  • Sequential Execution : Each service performs its task only when instructed by the orchestrator, ensuring that the steps occur in the correct order.
  • Compensation Logic : The orchestrator is also responsible for initiating compensating actions if something goes wrong. For example, if the inventory update fails, the orchestrator can command the payment service to refund the payment.

Advantages of Orchestration:

  • Centralized Control : With a single orchestrator, it’s easier to monitor, manage, and debug the transaction flow.
  • Simpler Logic : Since the orchestrator handles the flow, individual services don’t need to be aware of the overall transaction sequence.

Challenges of Orchestration:

  • Single Point of Failure : The orchestrator can become a bottleneck or single point of failure if not designed for high availability.
  • Tight Coupling to the Orchestrator : Services are dependent on the orchestrator, which can make the system less flexible compared to choreography.

3. Implementing the Simple Orchestration Saga Pattern: A Step-by-Step Guide

Let's consider the e-commerce scenario and implement it using the Saga pattern.

In our coffee purchasing scenario, each service represents a local transaction. The Coffee Service acts as the orchestrator of this saga, coordinating the other services to complete the purchase.

Here's a breakdown of how the saga might work:

  • Customer places an order : The customer places an order through the Order Service.
  • Coffee Service initiates the saga : The Coffee Service receives the order and initiates the saga.
  • Order Service creates an order : The Order Service creates a new order and persists it.
  • Billing Service calculates the cost : The Billing Service calculates the total cost of the order and creates a billing record.
  • Payment Service processes the payment : The Payment Service processes the payment.
  • Coffee Service updates order status : Once the payment is successful, the Coffee Service updates the order status to "completed".

How the Saga Pattern Resolves Distributed Transaction Issues: Methods and Real-World Example

3.1 Transaction entity

How the Saga Pattern Resolves Distributed Transaction Issues: Methods and Real-World Example

In my implementation of the saga, each SagaItemBuilder represents a step in our distributed transaction flow. The ActionBuilder defines the actions to be performed, including the main action and the rollback action that will be executed if an error occurs. The ActionBuilder encapsulates three pieces of information:

component : The bean instance where the method to be invoked resides.

method : The name of the method to be called.

args : The arguments to be passed to the method.

ActionBuilder

public class ActionBuilder {
    private Object component;
    private String method;
    private Object[] args;

    public static ActionBuilder builder() {
        return new ActionBuilder();
    }

    public ActionBuilder component(Object component) {
        this.component = component;
        return this;
    }

    public ActionBuilder method(String method) {
        this.method = method;
        return this;
    }

    public ActionBuilder args(Object... args) {
        this.args = args;
        return this;
    }

    public Object getComponent() { return component; }
    public String getMethod() { return method; }
    public Object[] getArgs() { return args; }
}

SagaItemBuilder

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class SagaItemBuilder {
    private ActionBuilder action;
    private Map, ActionBuilder> onBehaviour;

    public static SagaItemBuilder builder() {
        return new SagaItemBuilder();
    }

    public SagaItemBuilder action(ActionBuilder action) {
        this.action = action;
        return this;
    }

    public SagaItemBuilder onBehaviour(Class extends Exception> exception, ActionBuilder action) {
        if (Objects.isNull(onBehaviour)) onBehaviour = new HashMap();
        onBehaviour.put(exception, action);
        return this;
    }

    public ActionBuilder getAction() {
        return action;
    }

    public Map, ActionBuilder> getBehaviour() {
        return onBehaviour;
    }
}

Scenarios

import java.util.ArrayList;
import java.util.List;

public class Scenarios {
    List scenarios;

    public static Scenarios builder() {
        return new Scenarios();
    }

    public Scenarios scenario(SagaItemBuilder sagaItemBuilder) {
        if (scenarios == null) scenarios = new ArrayList();
        scenarios.add(sagaItemBuilder);
        return this;
    }

    public List getScenario() {
        return scenarios;
    }
}

Bellow is how can I commit the distribute transaction.

package com.example.demo.saga;

import com.example.demo.saga.exception.CanNotRollbackException;
import com.example.demo.saga.exception.RollBackException;
import com.example.demo.saga.pojo.ActionBuilder;
import com.example.demo.saga.pojo.SagaItemBuilder;
import com.example.demo.saga.pojo.Scenarios;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;

@Component
public class DTC {

    public boolean commit(Scenarios scenarios) throws Exception {
        validate(scenarios);
        for (int i = 0; i = 0; i--) {
            SagaItemBuilder scenario = scenarios.getScenario().get(i);
            Map, ActionBuilder> behaviours = scenario.getBehaviour();
            Set> exceptions = behaviours.keySet();
            ActionBuilder actionWhenException = null;

            if (failStep == i) {
                for(Class extends Exception> exception: exceptions) {
                    if (exception.isInstance(currentStepFailException)) {
                        actionWhenException = behaviours.get(exception);
                    }
                }
                if (actionWhenException == null) actionWhenException = behaviours.get(RollBackException.class);
            } else {
                actionWhenException = behaviours.get(RollBackException.class);
            }

            Object bean = actionWhenException.getComponent();
            String method = actionWhenException.getMethod();
            Object[] args = actionWhenException.getArgs();
            try {
                invoke(bean, method, args);
            } catch (Exception e) {
                throw new CanNotRollbackException("Error in %s belong to %s. Can not rollback transaction".formatted(method, bean.getClass()));
            }
        }
    }

    private void validate(Scenarios scenarios) throws Exception {
        for (int i = 0; i , ActionBuilder> behaviours = scenario.getBehaviour();
            Set> exceptions = behaviours.keySet();
            if (exceptions.contains(null)) throw new Exception("Exception can not be null in scenario has method %s, bean %s " .formatted(action.getMethod(), action.getComponent().getClass()));
            if (!exceptions.contains(RollBackException.class)) throw new Exception("Missing default RollBackException in scenario has method %s, bean %s " .formatted(action.getMethod(), action.getComponent().getClass()));
        }
    }

    public String invoke(Object bean, String methodName, Object... args) throws Exception {
        try {
            Class>[] paramTypes = new Class[args.length];
            for (int i = 0; i  parameterType (Object o) {
        if (o instanceof Integer) {
           return int.class;
        } else if (o instanceof Boolean) {
            return boolean.class;
        } else if (o instanceof Double) {
            return double.class;
        } else if (o instanceof Float) {
            return float.class;
        } else if (o instanceof Long) {
            return long.class;
        } else if (o instanceof Short) {
            return short.class;
        } else if (o instanceof Byte) {
            return byte.class;
        } else if (o instanceof Character) {
            return char.class;
        } else {
            return o.getClass();
        }
    }
}

3.2 Using it

I have 3 services that call to external service: BillingService , OrderService , PaymentService.

OrderService

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class OrderService {

    public String prepareOrder(String name, int number) {
        System.out.println("Prepare order for %s with order id %d ".formatted(name, number));
        return "Prepare order for %s with order id %d ".formatted(name, number);
    }

    public void Rollback_prepareOrder_NullPointException() {
        System.out.println("Rollback prepareOrder because NullPointException");
    }

    public void Rollback_prepareOrder_RollBackException() {
        System.out.println("Rollback prepareOrder because RollBackException");
    }
}

BillingService

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class BillingService {

    public String prepareBilling(String name, int number) {
        System.out.println("Prepare billing for %s with order id %d ".formatted(name, number));
        return "Prepare billing for %s with order id %d ".formatted(name, number);
    }

    public String createBilling(String name, int number) {
        System.out.println("Create billing for %s with order id %d ".formatted(name, number));
        return "Create billing for %s with order id %d ".formatted(name, number);
    }

    public void Rollback_prepareBilling_NullPointException() {
        System.out.println("Rollback prepareBilling because NullPointException");
    }

    public void Rollback_prepareBilling_ArrayIndexOutOfBoundsException() {
        System.out.println("Rollback prepareBilling because ArrayIndexOutOfBoundsException");
    }

    public void Rollback_prepareBilling_RollBackException() {
        System.out.println("Rollback prepareBilling because RollBackException");
    }

    public void Rollback_createBilling_NullPointException() {
        System.out.println("Rollback createBilling because NullPointException");
    }

    public void Rollback_createBilling_ArrayIndexOutOfBoundsException() {
        System.out.println("Rollback createBilling because ArrayIndexOutOfBoundsException");
    }

    public void Rollback_createBilling_RollBackException() {
        System.out.println("Rollback createBilling because RollBackException");
    }
}

PaymentService

package com.example.demo.service;

import org.springframework.stereotype.Service;

@Service
public class PaymentService {

    public String createPayment() {
        System.out.println("Create payment");
        return "Create payment";
    }

    public void Rollback_createPayment_NullPointException() {
        System.out.println("Rollback createPayment because NullPointException");
    }

    public void Rollback_createPayment_RollBackException() {
        System.out.println("Rollback createPayment because RollBackException");
    }
}

And in Coffee Service, I implement it as follows, I create a scenario and then commit it.

package com.example.demo.service;

import com.example.demo.saga.DTC;
import com.example.demo.saga.exception.RollBackException;
import com.example.demo.saga.pojo.ActionBuilder;
import com.example.demo.saga.pojo.SagaItemBuilder;
import com.example.demo.saga.pojo.Scenarios;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class CoffeeService {

    @Autowired
    private OrderService orderService;

    @Autowired
    private BillingService billingService;

    @Autowired
    private PaymentService paymentService;

    @Autowired
    private DTC dtc;

    public String test() throws Exception {
        Scenarios scenarios = Scenarios.builder()
                .scenario(
                        SagaItemBuilder.builder()
                                .action(ActionBuilder.builder().component(orderService).method("prepareOrder").args("tuanh.net", 123))
                                .onBehaviour(NullPointerException.class, ActionBuilder.builder().component(orderService).method("Rollback_prepareOrder_NullPointException").args())
                                .onBehaviour(RollBackException.class, ActionBuilder.builder().component(orderService).method("Rollback_prepareOrder_RollBackException").args())
                ).scenario(
                        SagaItemBuilder.builder()
                                .action(ActionBuilder.builder().component(billingService).method("prepareBilling").args("tuanh.net", 123))
                                .onBehaviour(NullPointerException.class, ActionBuilder.builder().component(billingService).method("Rollback_prepareBilling_NullPointException").args())
                                .onBehaviour(RollBackException.class, ActionBuilder.builder().component(billingService).method("Rollback_prepareBilling_RollBackException").args())
                ).scenario(
                         SagaItemBuilder.builder()
                                .action(ActionBuilder.builder().component(billingService).method("createBilling").args("tuanh.net", 123))
                                .onBehaviour(NullPointerException.class, ActionBuilder.builder().component(billingService).method("Rollback_createBilling_ArrayIndexOutOfBoundsException").args())
                                .onBehaviour(RollBackException.class, ActionBuilder.builder().component(billingService).method("Rollback_createBilling_RollBackException").args())
                ).scenario(
                        SagaItemBuilder.builder()
                                .action(ActionBuilder.builder().component(paymentService).method("createPayment").args())
                                .onBehaviour(NullPointerException.class, ActionBuilder.builder().component(paymentService).method("Rollback_createPayment_NullPointException").args())
                                .onBehaviour(RollBackException.class, ActionBuilder.builder().component(paymentService).method("Rollback_createPayment_RollBackException").args())
                );
        dtc.commit(scenarios);
        return "ok";
    }
}

3.3 Result

When i make a exception in create billing.

public String createBilling(String name, int number) {
    throw new NullPointerException();
}

Result

2024-08-24T14:21:45.445 07:00 INFO 19736 --- [demo] [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path '/'
2024-08-24T14:21:45.450 07:00 INFO 19736 --- [demo] [main] com.example.demo.DemoApplication : Started DemoApplication in 1.052 seconds (process running for 1.498)
2024-08-24T14:21:47.756 07:00 INFO 19736 --- [demo] [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-08-24T14:21:47.756 07:00 INFO 19736 --- [demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2024-08-24T14:21:47.757 07:00 INFO 19736 --- [demo] [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
Prepare order for tuanh.net with order id 123 
Prepare billing for tuanh.net with order id 123 
Rollback createBilling because RollBackException
Rollback prepareBilling because RollBackException
Rollback prepareOrder because RollBackException

Check out my GitHub Repository

4. Conclusion

In summary, the Saga pattern provides a robust solution for managing distributed transactions by breaking them down into smaller, manageable steps. The choice between Choreography and Orchestration depends on the specific needs and architecture of your system. Choreography offers loose coupling and resilience, while Orchestration provides centralized control and easier monitoring. By carefully designing your system with the Saga pattern, you can achieve consistency, availability, and flexibility in your distributed microservices architecture.

Feel free to comment below if you have any questions or need further clarification on implementing the Saga pattern in your system!

Read posts more at : How the Saga Pattern Resolves Distributed Transaction Issues: Methods and Real-World Example

版本声明 本文转载于:https://dev.to/anh_trntun_4732cf3d299/how-the-saga-pattern-resolves-distributed-transaction-issues-methods-and-real-world-example-48ki?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • Turborepo 与 Nx:哪种 Monorepo 工具适合您?
    Turborepo 与 Nx:哪种 Monorepo 工具适合您?
    随着现代开发变得越来越复杂,monorepos变得越来越流行。它们允许将多个项目或包存储在单个存储库中,从而简化依赖关系管理并促进更好的协作。用于管理 monorepos 的两个顶级工具是 Turborepo 和 Nx。 这两种工具都旨在提高处理单一存储库的效率和可扩展性,但它们具有独特的优势。在本...
    编程 发布于2024-11-07
  • Java 数组简介
    Java 数组简介
    编程通常涉及管理和操作大量数据,对此高效且有效的数据结构至关重要。数组是计算机科学中的基本数据结构,提供了一种存储固定大小的相同类型元素序列的方法。在本博客中,我们将深入了解 Java 中的数组:了解它们是什么、它们的语法、如何对它们进行操作以及它们的内存管理。 为什么我们需要数组?...
    编程 发布于2024-11-07
  • 解决 CORS 问题的方法
    解决 CORS 问题的方法
    要解决 CORS 问题,您需要在 Web 服务器(如 Apache 或 Nginx)、后端(如 Django、Go 或 Node.js)中添加适当的标头,或在前端框架(如 React 或 Next.js)中。以下是每个平台的步骤: 1. 网络服务器 阿帕奇 您可以在 ...
    编程 发布于2024-11-07
  • 内存对齐如何影响 C 结构的大小?
    内存对齐如何影响 C 结构的大小?
    C 结构中的内存对齐使用 C 结构时,理解内存对齐至关重要。内存对齐是指将数据在内存中放置在特定的边界处。在 32 位机器上,内存通常按 4 字节边界对齐。结构的内存对齐考虑以下结构:typedef struct { unsigned short v1; unsigned short...
    编程 发布于2024-11-07
  • 受顶级旅游景点启发构建创新项目:令人难忘的旅行体验开发人员指南
    受顶级旅游景点启发构建创新项目:令人难忘的旅行体验开发人员指南
    作为开发商,我们经常从周围的世界中汲取灵感——还有什么比令人难以置信的旅游景点更好的来源呢?无论您是在开发旅行应用程序、沉浸式体验还是基于位置的服务,了解目的地的脱颖而出都是关键。看看这份关于阿尔巴尼亚最佳旅游景点的终极指南,为您的下一个创意项目提供动力,并了解这些地标如何在现实世界和数字世界中塑造...
    编程 发布于2024-11-07
  • 如何使用 std::locale 在 C++ 中使用逗号格式化数字?
    如何使用 std::locale 在 C++ 中使用逗号格式化数字?
    在 C 中用逗号格式化数字 在 C 中,std::locale 类提供了一种依赖于区域设置的方式用逗号格式化数字.std::locale 与 std::stringstream要将数字格式化为带逗号的字符串,可以将 std::locale 与 std::stringstream 一起使用如下:#in...
    编程 发布于2024-11-07
  • 如何避免在 Python 中打印素数序列中的奇数?
    如何避免在 Python 中打印素数序列中的奇数?
    如何在 Python 中打印素数序列许多程序员都在努力创建一个在 Python 中准确打印素数的函数。一个常见的问题是打印奇数列表。要解决这个问题,必须彻底了解素数属性并修改代码。素数只能被 1 和它们本身整除。因此,改进的代码检查从 2 到数字的平方根(如果数字较小,则为数字本身)范围内的整除性。...
    编程 发布于2024-11-07
  • 如何在 Pygame 中向鼠标方向发射子弹?
    如何在 Pygame 中向鼠标方向发射子弹?
    如何在 Pygame 中朝鼠标方向发射子弹在 Pygame 中,可以创建一颗朝鼠标方向发射的子弹。为此,需要创建一个代表子弹的类,并根据鼠标位置设置其初始位置和方向。子弹的类首先,为项目符号创建一个类。该类应包含子弹的位置、大小和表面的属性。表面就是将在屏幕上渲染的内容。import pygame ...
    编程 发布于2024-11-07
  • 优化性能的 GG 编码技巧:加快代码速度
    优化性能的 GG 编码技巧:加快代码速度
    在软件开发领域,优化代码性能对于交付用户喜爱的快速响应的应用程序至关重要。无论您从事前端还是后端工作,学习如何编写高效的代码都是至关重要的。在本文中,我们将探讨各种性能优化技术,例如降低时间复杂度、缓存、延迟加载和并行性。我们还将深入探讨如何分析和优化前端和后端代码。让我们开始提高代码的速度和效率!...
    编程 发布于2024-11-07
  • 如何使用 PHP 的 strtotime() 函数查找一周中特定一天的日期?
    如何使用 PHP 的 strtotime() 函数查找一周中特定一天的日期?
    确定一周中指定日期(星期一、星期二等)的日期如果您需要确定日期戳一周中的特定一天,例如星期一、星期二或任何其他工作日,可以使用 strtotime() 函数。当指定日期在本周内尚未出现时,此函数特别有用。例如,要获取下周二的日期戳,只需使用以下代码:strtotime('next tuesday')...
    编程 发布于2024-11-07
  • 使用 Socket.io 和 Redis 构建和部署聊天应用程序。
    使用 Socket.io 和 Redis 构建和部署聊天应用程序。
    在本教程中,我们将使用 Web 套接字构建一个聊天应用程序。当您想要构建需要实时传输数据的应用程序时,Web 套接字非常有用。 在本教程结束时,您将能够设置自己的套接字服务器、实时发送和接收消息、在 Redis 中存储数据以及在渲染和 google cloud run 上部署您的应用程序。 ...
    编程 发布于2024-11-07
  • SQL 连接内部
    SQL 连接内部
    SQL 连接是查询数据库的基础,它允许用户根据指定条件组合多个表中的数据。连接分为两种主要类型:逻辑连接和物理连接。逻辑联接代表组合表中数据的概念方式,而物理联接是指这些联接在数据库系统(例如 RDS(关系数据库服务)或其他 SQL 服务器)中的实际实现。在今天的博文中,我们将揭开 SQL 连接的神...
    编程 发布于2024-11-07
  • 你应该知道的 Javascript 特性
    你应该知道的 Javascript 特性
    在本文中,我们将探讨如何在尝试访问可能是未定义或 null 的数据时防止错误,并且我们将介绍您可以使用的方法用于在必要时有效管理数据。 通过可选链接进行安全访问 在 JavaScript 中,当尝试访问嵌套对象中的值或函数时,如果结果为 undefined,您的代码可能会引发错误。此...
    编程 发布于2024-11-07
  • JavaScript 中的 Promise:理解、处理和掌握异步代码
    JavaScript 中的 Promise:理解、处理和掌握异步代码
    简介 我曾经是一名 Java 开发人员,我记得第一次接触 JavaScript 中的 Promise 时。尽管这个概念看起来很简单,但我仍然无法完全理解 Promise 是如何工作的。当我开始在项目中使用它们并了解它们解决的案例时,情况发生了变化。然后灵光乍现的时刻到来了,一切都变...
    编程 发布于2024-11-07
  • 如何将密钥集成到 Java Spring Boot 中
    如何将密钥集成到 Java Spring Boot 中
    Java Spring Boot 中的密钥简介 密钥提供了一种现代、安全的方式来验证用户身份,而无需依赖传统密码。在本指南中,我们将引导您使用 Thymeleaf 作为模板引擎将密钥集成到 Java Spring Boot 应用程序中。 我们将利用 Corbado 的密钥优先 UI...
    编程 发布于2024-11-07

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3