使用 Spring Boot 进行单元测试

【注】本文译自: Unit Testing with Spring Boot – Reflectoring

编写好的单元测试可以被认为是一门难以掌握的艺术。但好消息是支持它的机制很容易学习。
本教程为您提供了这些机制,并详细介绍了编写良好的单元测试所必需的技术细节,重点是 Spring Boot 应用程序。
我们将看看如何以可测试的方式创建 Spring bean,然后讨论 Mockito 和 AssertJ 的用法,这两个库默认包含在 Spring Boot 中用于测试。
请注意,本文仅讨论单元测试。集成测试、Web 层测试和持久层测试将在本系列的后续文章中讨论。

 代码示例

本文附有 GitHub 上 的工作代码示例。

依赖关系

对于本教程中的单元测试,我们将使用 JUnit Jupiter (JUnit 5)、Mockito 和 AssertJ。我们还将包括 Lombok 以减少一些样板代码:

dependencies {
    compileOnly('org.projectlombok:lombok')
    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile('org.junit.jupiter:junit-jupiter:5.4.0')
    testCompile('org.mockito:mockito-junit-jupiter:2.23.0')
}

Mockito 和 AssertJ 是使用 spring-boot-starter-test 依赖项自动导入的,但我们必须自己包含 Lombok。

不要在单元测试中使用 Spring

如果你以前用 Spring 或 Spring Boot 写过测试,你可能会说我们不需要 Spring 来写单元测试。这是为什么?
考虑以下测试 RegisterUseCase 类的单个方法的“单元”测试:

@ExtendWith(SpringExtension.class)
@SpringBootTest
class RegisterUseCaseTest {

    @Autowired
    private RegisterUseCase registerUseCase;

    @Test
    void savedUserHasRegistrationDate() {
        User user = new User("zaphod", "zaphod@mail.com");
        User savedUser = registerUseCase.registerUser(user);
        assertThat(savedUser.getRegistrationDate()).isNotNull();
    }

}

这个测试在我电脑上的一个空 Spring 项目上运行大约需要 4.5 秒。
但是一个好的单元测试只需要几毫秒。否则它会阻碍由测试驱动开发(TDD)思想推动的“测试/代码/测试”流程。但即使我们不采用 TDD,等待太长时间的测试也会破坏我们的注意力。
执行上面的测试方法实际上只需要几毫秒。 剩下的 4.5 秒是由于 @SpringBootRun 告诉 Spring Boot 设置整个 Spring Boot 应用程序上下文。
所以我们启动了整个应用程序只是为了将 RegisterUseCase 实例自动装配到我们的测试中。一旦应用程序变大并且 Spring 不得不将越来越多的 bean 加载到应用程序上下文中,它将花费更长的时间。
那么,为什么我们不应该在单元测试中使用 Spring Boot 呢?老实说,本教程的大部分内容都是关于在没有 Spring Boot 的情况下编写单元测试。

创建可测试的 Spring Bean

然而,我们可以做一些事情来提高 Spring bean 的可测试性。

字段注入是不可取的

让我们从一个不好的例子开始。考虑以下类:

@Service
public class RegisterUseCase {

    @Autowired
    private UserRepository userRepository;

    public User registerUser(User user) {
        return userRepository.save(user);
    }

}

这个类不能在没有 Spring 的情况下进行单元测试,因为它没有提供传递 UserRepository 实例的方法。那么,我们需要按照上一节中讨论的方式编写测试,让 Spring 创建一个 UserRepository 实例并将其注入到用 @Autowired 注解的字段中。

这里的教训是不要使用字段注入

提供构造函数

实际上,我们根本不要使用 @Autowired 注解:

@Service
public class RegisterUseCase {

    private final UserRepository userRepository;

    public RegisterUseCase(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User registerUser(User user) {
        return userRepository.save(user);
    }

}

这个版本通过提供允许传入 UserRepository 实例的构造函数来允许构造函数注入。在单元测试中,我们现在可以创建这样一个实例(可能是我们稍后讨论的模拟实例)并将其传递给构造函数。
在创建生产应用程序上下文时,Spring 将自动使用此构造函数来实例化 RegisterUseCase 对象。注意,在 Spring 5 之前,我们需要在构造函数中添加 @Autowired 注解,以便 Spring 找到构造函数。
还要注意 UserRepository 字段现在是 final。这是有道理的,因为字段内容在应用程序的生命周期内永远不会改变。它还有助于避免编程错误,因为如果我们忘记初始化字段,编译器会报错。

减少样板代码

使用 Lombok 的 @RequiredArgsConstructor 注解,我们可以让构造函数自动生成:

@Service
@RequiredArgsConstructor
public class RegisterUseCase {

    private final UserRepository userRepository;

    public User registerUser(User user) {
        user.setRegistrationDate(LocalDateTime.now());
        return userRepository.save(user);
    }

}

现在,我们有一个非常简洁的类,没有样板代码,可以在普通的 java 测试用例中轻松实例化:

class RegisterUseCaseTest {

    private UserRepository userRepository = ...;

    private RegisterUseCase registerUseCase;

    @BeforeEach
    void initUseCase() {
        registerUseCase = new RegisterUseCase(userRepository);
    }

    @Test
    void savedUserHasRegistrationDate() {
        User user = new User("zaphod", "zaphod@mail.com");
        User savedUser = registerUseCase.registerUser(user);
        assertThat(savedUser.getRegistrationDate()).isNotNull();
    }

}

然而,还缺少一点,那就是如何模拟我们被测类所依赖的 UserRepository 实例,因为我们不想依赖真实的东西,它可能需要连接到数据库。

使用 Mockito 来模拟依赖

现在事实上的标准模拟库是 Mockito。它至少提供了两种方法来创建模拟的 UserRepository 以填补前面代码示例中的空白。

使用普通 Mockito 模拟依赖项

第一种方法是以编程方式使用 Mockito:

private UserRepository userRepository = Mockito.mock(UserRepository.class);

这将创建一个从外部看起来像 UserRepository 的对象。默认情况下,当一个方法被调用时它什么都不做,如果该方法有返回值则返回 null
我们的测试现在将在 assertThat(savedUser.getRegistrationDate()).isNotNull() 处以 NullPointerException 失败,因为 userRepository.save(user) 现在返回 null
所以,我们必须告诉 Mockito 在调用 userRepository.save() 时返回一些东西。我们使用静态 when 方法来做到这一点:

    @Test
    void savedUserHasRegistrationDate() {
        User user = new User("zaphod", "zaphod@mail.com");
        when(userRepository.save(any(User.class))).then(returnsFirstArg());
        User savedUser = registerUseCase.registerUser(user);
        assertThat(savedUser.getRegistrationDate()).isNotNull();
    }

这将使 userRepository.save() 返回传递给方法的相同用户对象。
Mockito 具有更多功能,可以进行模拟、匹配参数和验证方法调用。有关更多信息,请查看参考文档

使用 Mockito 的 @Mock 注解模拟依赖项

创建模拟对象的另一种方法是 Mockito 的 @Mock 注解与 JUnit Jupiter 的 MockitoExtension 相结合:

@ExtendWith(MockitoExtension.class)
class RegisterUseCaseTest {

    @Mock
    private UserRepository userRepository;

    private RegisterUseCase registerUseCase;

    @BeforeEach
    void initUseCase() {
        registerUseCase = new RegisterUseCase(userRepository);
    }

    @Test
    void savedUserHasRegistrationDate() {
        // ...
    }

}

@Mock 注解指定了 Mockito 应该注入模拟对象的字段。 @MockitoExtension 告诉 Mockito 评估那些 @Mock 注解,因为 JUnit 不会自动执行此操作。
结果和手动调用 Mockito.mock() 一样,选择使用哪种方式是品味问题。 但是请注意,通过使用 MockitoExtension 将我们的测试绑定到测试框架。
请注意,我们也可以在 registerUseCase 字段上使用 @InjectMocks 注解,而不是手动构造 RegisterUseCase 对象。然后 Mockito 会按照指定的算法为我们创建一个实例:

@ExtendWith(MockitoExtension.class)
class RegisterUseCaseTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private RegisterUseCase registerUseCase;

    @Test
    void savedUserHasRegistrationDate() {
        // ...
    }

}

使用 AssertJ 创建可读断言

Spring Boot 测试支持自动附带的另一个库是 AssertJ。我们已经在上面使用它来实现我们的断言:

assertThat(savedUser.getRegistrationDate()).isNotNull();

然而,让断言更具可读性不是更好吗?例如:

assertThat(savedUser).hasRegistrationDate();

在很多情况下,像这样的小改动会使测试更容易理解。因此,让我们在测试源文件夹中创建我们自己的自定义断言

class UserAssert extends AbstractAssert<UserAssert, User> {

    UserAssert(User user) {
        super(user, UserAssert.class);
    }

    static UserAssert assertThat(User actual) {
        return new UserAssert(actual);
    }

    UserAssert hasRegistrationDate() {
        isNotNull();
        if (actual.getRegistrationDate() == null) {
            failWithMessage(
                    "Expected user to have a registration date, but it was null"
            );
        }
        return this;
    }
}

现在,如果我们从新的 UserAssert 类而不是从 AssertJ 库导入 assertThat 方法,我们就可以使用新的、更易于阅读的断言。
创建像这样的自定义断言似乎需要很多工作,但实际上只需几分钟即可完成。我坚信投入这些时间来创建可读的测试代码是值得的,即使之后它的可读性只是稍微好一点。毕竟,我们只编写一次测试代码,其他人(包括“未来的我”)必须在软件的生命周期中多次阅读、理解和操作代码
如果仍然觉得工作量太大,请查看 AssertJ 的断言生成器

结论

在测试中启动 Spring 应用程序是有原因的,但对于普通的单元测试来说,这是没有必要的。由于更长的周转时间,它甚至是有害的。相反,我们应该以一种易于支持为其编写简单单元测试的方式构建我们的 Spring bean。
Spring Boot Test Starter 附带 Mockito 和 AssertJ 作为测试库。
让我们利用这些测试库来创建富有表现力的单元测试!
最终形式的代码示例可在 github 上 找到。

Java ArrayList 与 LinkedList

【注】本文译自: Java ArrayList vs LinkedList | Baeldung

1. 概述

对于 collections (集合),Java 标准库提供了大量可供选择的选项。在这些选项中,有两个著名的 List 实现,称为 ArrayListLinkedList,每个实现都有自己的属性和用例。
在本教程中,我们将看到这两者是如何实现的。然后,我们将为评估每个应用的不同。

2. ArrayList

在内部,ArrayList 使用数组来实现 List 接口。由于数组在 Java 中是固定大小的,因此 ArrayList 创建一个具有一些初始容量的数组。在此过程中,如果我们需要存储比默认容量更多的项,它将用一个新的、更大的数组替换该数组。
为了更好地理解它的属性,让我们根据它的三个主要操作来评估这个数据结构:添加项、通过索引获取项和通过索引删除项。

2.1. 添加

当我们创建一个空的 ArrayList 时,它会使用默认容量(当前为 10)初始化其后备数组:

在该数组尚未满时添加新项目就像将该项分配给特定数组索引一样简单。这个数组索引由当前数组大小决定,因为我们实际上是附加到列表中:

backingArray[size] = newItem;
size++;

因此,在最佳和一般情况下,加法操作的时间复杂度为 O(1),这非常快。但是,随着后备数组变满,添加实现的效率会降低:

要添加新项目,我们应该首先初始化一个容量更大的全新数组,并将所有现有项目复制到新数组中。只有在复制当前元素后,我们才能添加新项目。因此,在最坏的情况下时间复杂度为 O(n),因为我们必须先复制 n 个元素。
从理论上讲,添加新元素的运行时间为摊销常数。也就是说,添加 n 个元素需要 O(n) 时间。但是,由于复制开销,某些单次添加可能表现不佳。

2.2. 按索引访问

通过索引访问项是 ArrayList 的真正亮点。要检索下标为 i 的项,我们只需要返回位于后备数组中第 i 个下标的项。因此,通过索引操作访问的时间复杂度始终为 O(1)

2.3. 通过索引删除

假设我们要从 ArrayList 中删除索引 6,它对应于我们的后备数组中的元素 15:

将所需元素标记为已删除后,我们应该将其后的所有元素向后移动一个索引。显然,元素越靠近数组的开头,我们应该移动的元素就越多。因此,时间复杂度在最佳情况下为 O(1),在平均和最坏情况下为 O(n)。

2.4. 应用和限制

.通常,当需要 List 实现时,ArrayList 是许多开发人员的默认选择。事实上,当读取次数远远超过写入次数时,这实际上是一个明智的选择
有时我们需要同样频繁的读取和写入。如果我们确实估计了可能项目的最大数量,那么使用 ArrayList 仍然有意义。如果是这种情况,我们可以使用初始容量初始化 ArrayList:

int possibleUpperBound = 10_000;
List<String> items = new ArrayList<>(possibleUpperBound);

这种估计可以防止大量不必要的复制和数组分配。

此外,数组由 Java 中的 int 值索引。因此,在 Java 数组中存储超过 2 的 32 次方个元素是不可能的,因此,在 ArrayList 中也是如此

3. LinkedList

LinkedList,顾名思义,使用链接节点的集合来存储和检索元素。例如,以下是添加四个元素后的 Java 实现:

每个节点维护两个指针:一个指向下一个元素,另一个指向前一个元素。对此进行扩展,双向链表有两个指向第一项和最后一项的指针
同样,让我们根据相同的基本操作来评估这个实现。

3.1. 添加

为了添加新节点,首先,我们应该将当前最后一个节点链接到新节点:

然后更新最后一个指针:

由于这两个操作都很简单,因此加法操作的时间复杂度始终为 O(1)

3.2. 通过索引访问

LinkedList 与 ArrayList 不同,不支持快速随机访问。因此,为了按索引查找元素,我们应该手动遍历列表的某些部分

在最好的情况下,当请求的项目接近列表的开头或结尾时,时间复杂度将与 O(1) 一样快。然而,在平均和最坏情况下,我们可能会以 O(n) 的访问时间结束,因为我们必须一个接一个地检查许多节点。

3.3. 通过索引删除

为了删除一项,我们应该首先找到请求的项,然后从列表中取消它的链接。因此,访问时间决定了时间复杂度——即在最佳情况下为 O(1),在平均和最坏情况下为 O(n)。

3.4. 应用

当添加率远高于读取率时,LinkedLists更适合。
此外,当大多数时候我们想要第一个或最后一个元素时,它可以用于读取密集的场景。值得一提的是,LinkedList 还实现了 Deque 接口——支持对集合两端的高效访问。
通常,如果我们知道它们的实现差异,那么我们可以轻松地为特定用例选择一个。
例如,假设我们将在类似列表的数据结构中存储大量时间序列事件。我们知道我们每秒都会收到突发事件。
此外,我们需要定期检查所有事件并提供一些统计数据。对于此用例,LinkedList 是更好的选择,因为添加速率远高于读取速率。
此外,我们会读取所有项目,因此我们无法超过 O(n) 上限。

4. 结论

在本教程中,我们首先深入研究了 ArrayListLinkLists 如何在 Java 中实现。
我们还评估了其中每一个的不同用例。

Java hashCode() 指南

【注】本文译自:Guide to hashCode() in Java | Baeldung

Java hashCode() 指南

1. 概述

    哈希是计算机科学的一个基本概念。
    在 Java 中,高效的哈希算法支持一些最流行的集合,例如 HashMap(查看这篇深入的 文章)和 HashSet。
    在本教程中,我们将重点介绍 hashCode() 的工作原理、它如何在集合中处理以及如何正确实现它。

2. 在数据结构中使用 hashCode()

    在某些情况下,最简单的集合操作可能效率低下。
    举例来说,这会触发线性搜索,这对于大型列表效率非常低:

List<String> words = Arrays.asList("Welcome", "to", "Baeldung");
if (words.contains("Baeldung")) {
    System.out.println("Baeldung is in the list");
}

    Java 提供了许多数据结构来专门处理这个问题。 例如,几个 Map 接口实现是 hash tables(哈希表)。
    使用哈希表时,这些集合使用 hashCode() 方法计算给定键的哈希值。然后他们在内部使用这个值来存储数据,以便访问操作更加高效。

3. 了解 hashCode() 的工作原理

    简而言之,hashCode() 返回一个由散列算法生成的整数值。
    相等的对象(根据它们的 equals())必须返回相同的哈希码。不同的对象不需要返回不同的哈希码
    hashCode() 的通用契约声明:

  • 在 Java 应用程序执行期间,只要在同一对象上多次调用它,hashCode() 必须始终返回相同的值,前提是对象上的 equals 比较中使用的信息没有被修改。这个值不需要从应用程序的一次执行到同一应用程序的另一次执行保持一致。
  • 如果根据 equals(Object) 方法两个对象相等,则对这两个对象中的每一个调用 hashCode() 方法必须产生相同的值。
  • 如果根据 equals(java.lang.Object) 方法两个对象不相等,则对这两个对象中的每一个调用 hashCode 方法不需要产生不同的整数结果。但是,开发人员应该意识到,为不相等的对象生成不同的整数结果可以提高哈希表的性能。

“在合理可行的情况下,类 Object 定义的 hashCode() 方法确实为不同的对象返回不同的整数。(这通常通过将对象的内部地址转换为整数来实现,但 JavaTM 编程语言不需要这种实现技术。)”

4. 一个简单的 hashCode() 实现

    一个完全符合上述约定的简单 hashCode() 实现实际上非常简单。
    为了演示这一点,我们将定义一个示例 User 类来覆盖该方法的默认实现:

public class User {

    private long id;
    private String name;
    private String email;

    // standard getters/setters/constructors
    @Override
    public int hashCode() {
        return 1;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o)
            return true;
        if (o == null)
            return false;
        if (this.getClass() != o.getClass())
            return false;
        User user = (User) o;
        return id == user.id && (name.equals(user.name) && email.equals(user.email));
    }
    // getters and setters here
}

    User 类为完全遵守各自合同的 equals() 和 hashCode() 提供自定义实现。更重要的是,让 hashCode() 返回任何固定值并没有什么不合法的。
    但是,这种实现将哈希表的功能降级到基本上为零,因为每个对象都将存储在同一个单个存储桶中。
在这种情况下,哈希表查找是线性执行的,并没有给我们带来任何真正的优势。我们将在第 7 节详细讨论。

5. 改进 hashCode() 实现

    让我们通过包含 User 类的所有字段来改进当前的 hashCode() 实现,以便它可以为不相等的对象产生不同的结果:

@Override
public int hashCode() {
    return (int) id * name.hashCode() * email.hashCode();
}

    这个基本的散列算法绝对比前一个好得多。这是因为它仅通过将 name 和 email 字段的哈希码与 id 相乘来计算对象的哈希码。
一般来说,我们可以说这是一个合理的 hashCode() 实现,只要我们保持 equals() 实现与其一致。6. 标准 hashCode() 实现

    我们用来计算哈希码的哈希算法越好,哈希表的性能就越好。
让我们看看一个“标准”实现,它使用两个素数为计算出的哈希码添加更多的唯一性:

@Override
public int hashCode() {
    int hash = 7;
    hash = 31 * hash + (int) id;
    hash = 31 * hash + (name == null ? 0 : name.hashCode());
    hash = 31 * hash + (email == null ? 0 : email.hashCode());
    return hash;
}

    虽然我们需要了解 hashCode() 和 equals() 方法所扮演的角色,但我们不必每次都从头开始实现它们。这是因为大多数 IDE 可以生成自定义 hashCode() 和 equals() 实现。从 Java 7 开始,我们有一个 Objects.hash() 实用方法来进行舒适的散列:

Objects.hash(name, email)

    IntelliJ IDEA 生成以下实现:

@Override
public int hashCode() {
    int result = (int) (id ^ (id >>> 32));
    result = 31 * result + name.hashCode();
    result = 31 * result + email.hashCode();
    return result;
}

    Eclipse 产生了这个:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((email == null) ? 0 : email.hashCode());
    result = prime * result + (int) (id ^ (id >>> 32));
    result = prime * result + ((name == null) ? 0 : name.hashCode());
    return result;
}

    除了上述基于 IDE 的 hashCode() 实现之外,还可以自动生成高效的实现,例如使用 Lombok.。
在这种情况下,我们需要在 pom.xml 中添加 lombok-maven 依赖:

<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok-maven</artifactId>
  <version>1.16.18.0</version>
  <type>pom</type>
</dependency>

    现在用@EqualsAndHashCode 注解 User 类就足够了:

@EqualsAndHashCode
public class User {
    // fields and methods here
}

    同样,如果我们希望 Apache Commons Lang 的 HashCodeBuilder 类为我们生成 hashCode() 实现,我们在 pom 文件中包含 commons-lang Maven 依赖项:

<dependency>
  <groupId>commons-lang</groupId>
  <artifactId>commons-lang</artifactId>
  <version>2.6</version>
</dependency>

    hashCode() 可以这样实现:

public class User {
    public int hashCode() {
        return new HashCodeBuilder(17, 37).
        append(id).
        append(name).
        append(email).
        toHashCode();
    }
}

    一般来说,在实现 hashCode() 时没有通用的方法。我们强烈推荐阅读 Joshua Bloch 的 Effective Java.。它提供了实现高效散列算法的详尽指南列表。
    请注意,所有这些实现都以某种形式使用了数字 31。这是因为 31 有一个很好的属性。它的乘法可以用按位移位代替,这比标准乘法要快:

31 * i == (i << 5) - i

7. 处理哈希冲突

    哈希表的内在行为带来了这些数据结构的一个相关方面:即使使用有效的哈希算法,两个或多个对象可能具有相同的哈希码,即使它们不相等。因此,即使它们具有不同的散列表键,它们的散列码也会指向同一个桶。

    这种情况通常被称为哈希冲突,有多种处理方法,每种方法都有其优点和缺点。Java 的 HashMap 使用单独的链接方法来处理冲突:
    “当两个或多个对象指向同一个存储桶时,它们只是存储在一个链表中。在这种情况下,哈希表是一个链表数组,每个具有相同哈希值的对象都附加到链表中的桶索引处。
在最坏的情况下,几个桶会绑定一个链表,而对链表中对象的检索将是线性执行的。”

    哈希冲突方法简单说明了高效实现 hashCode() 的重要性。
    Java 8 为 HashMap 实现带来了有趣的增强。如果桶大小超过特定阈值,则树图替换链表。这允许实现 O(logn) 查找而不是悲观 O(n)。

8. 创建一个简单的应用程序

    现在我们将测试标准 hashCode() 实现的功能。
    让我们创建一个简单的 Java 应用程序,将一些 User 对象添加到 HashMap 并使用 SLF4J 在每次调用该方法时将消息记录到控制台。
    这是示例应用程序的入口点:

public class Application {

    public static void main(String[] args) {
        Map<User, User> users = new HashMap<>();
        User user1 = new User(1L, "John", "john@domain.com");
        User user2 = new User(2L, "Jennifer", "jennifer@domain.com");
        User user3 = new User(3L, "Mary", "mary@domain.com");

        users.put(user1, user1);
        users.put(user2, user2);
        users.put(user3, user3);
        if (users.containsKey(user1)) {
            System.out.print("User found in the collection");
        }
    }
}

    这是 hashCode() 实现:

public class User {

    // ...

    public int hashCode() {
        int hash = 7;
        hash = 31 * hash + (int) id;
        hash = 31 * hash + (name == null ? 0 : name.hashCode());
        hash = 31 * hash + (email == null ? 0 : email.hashCode());
        logger.info("hashCode() called - Computed hash: " + hash);
        return hash;
    }
}

    这里需要注意的是,每次在哈希映射中存储对象并使用 containsKey() 方法检查时,都会调用 hashCode() 并将计算出的哈希码打印到控制台:

[main] INFO com.baeldung.entities.User - hashCode() called - Computed hash: 1255477819
[main] INFO com.baeldung.entities.User - hashCode() called - Computed hash: -282948472
[main] INFO com.baeldung.entities.User - hashCode() called - Computed hash: -1540702691
[main] INFO com.baeldung.entities.User - hashCode() called - Computed hash: 1255477819
User found in the collection

9. 结论

    很明显,生成高效的 hashCode() 实现通常需要混合一些数学概念(即素数和任意数)、逻辑和基本数学运算。
    无论如何,我们可以有效地实现 hashCode() ,而无需使用这些技术。我们只需要确保散列算法为不相等的对象生成不同的哈希码,并且它与 equals() 的实现一致。
    与往常一样,本文中显示的所有代码示例都可以在 GitHub 上找到

DevOps 发展史

【注】本文节译自:https://blog.devops4me.com/history-of-devops/

起源

  软件开发生命周期(SDLC)的发展迅速改变了组织如何将其产品发布/生产到生产环境的格局。当谈到组织如何管理产品交付时,传统的 SDLC 通常与瀑布(Waterfall)模式联系在一起。瀑布模式不能适应与组织的业务目标,他们希望为客户提供更快的速度和功能/产品。照此逻辑,他们需要一种新的方法来加快产品交付,但同时还要改善开发人员IT 运营团队。因此,DevOps 就出现了,2008 年多伦多敏捷会议上,Petrick Debois 介绍了“DevOps”一词。
  第一次会议名为 Devopsdays,于 2009 年在比利时根特举行。比利时顾问,项目经理和敏捷实践者 Patrick Debois 创立了会议。该会议现已传播到其他国家。2012年,DevOps 状态报告由 Puppet 的 Alanna Brown 起草并发布。截至 2014 年,Nicole Forsgren、Gene Kim、Jez Humble 等人发布了年度 DevOps 状态报告。在2014年,他们发现 DevOp 的采用正在加速。同样在 2014 年,Lisa Crispin 和 Janet Gregory 撰写了 More Agile Testing,其中包括有关测试和 DevOps 的章节。
  如果我将上面的时间事件放到时间轴中,如下图所示:

什么是 DevOps?

  DevOps 也是新兴技术和新兴商业文化的结合。转向 DevOps 文化的想法是建立开放的沟通,透明性和跨学科团队合作。DevOps背后的概念打破了孤岛,并为开发人员(DEV)与 IT 运营(OPS)之间的讨论和协作创造了更多空间。DevOps 的力量在于支持它的文化,使人们的思维方式从孤岛上移开了。它通常可以帮助您了解其来源、为什么变得流行以及什么使它流行。

目的是什么?

  在实施 DevOps 文化和方法时,DevOps 可以解决您组织面临的挑战,并且组织将获得:

  • 更快的服务交付:紧跟快速需求的敏捷版本。跨数据可见性:确保合规性和数据准确性。
  • 服务效率:提高质量和性能。
  • 经验丰富的专业DevOps:教您成功所需的工具。
  • 全面迎合组织的特定需求。

    DevOps 是:

  • 概念
  • 心态
  • 个人理解和拥护的共同态度
  • 必须培育和反复改进的文化
  • 可见度
  • 指导
  • 学习
  • 包容和开放的所有想法
  • 迭代
  • 持续
  • 协同合作
  • 自信地开发和交付软件的绝佳方法

    DevOps 不是:

  • 轻松实现或实施
  • 产品或工具链
  • 职务或职位
  • 云基础架构解决方案
  • 一项技术
  • 一种编程语言
  • 营销活动
  • CI / CD 流水线
  • Kubernetes
  • 容器 / Docker
  • 开源软件
  • 基础设施即代码
  • 自动化

    简而言之

      DevOps 认为 IT 行业急需概念上的不断学习和改进。而且,IT 社区无疑可以从 DevOps 历史中学到很多东西。这场 DevOps 革命不足为奇,而且随着创新的不断发展,其重要性在未来会不断提高。我们已经看到安全性与 DevOps 结合可以如何永远改变 Infosec 行业。

    结论

      将 DevOps 描述为一个旅程或愿望,而不是定义的目标或工具是合理的。DevOps 寻求持续的改进、更多的输出、更高的效率、甚至持续部署。支持 DevOps 的自动化工具还在不断发展。

DevOps教程:什么是DevOps

【注】本文译自: https://www.javatpoint.com/devops

  DevOps 是两个单词的复合,一个是 Development,另一个是 Operations。它是一种共同提升开发和运维过程的文化。
  DevOps 教程将帮助你学习 DevOps 基础知识并带你深入了解各种 DevOps 工具,譬如:Git、Ansible、Docker、Puppet、Jenkins、Chef、NagiosKubernetes

什么是 DevOps?

  DevOps 是两个单词的复合,其一是软件开发,其二是运维。这就允许一个团队掌握整个应用生命周期,从开发测试部署以及运维。DevOps 有助于减少软件开发工程师、质量保障(QA)工程师和系统管理者之间的断层。

  DevOps 提升开发和运维团队间的协作,通过自动化和可重复的方式将将代码更快地部署到生产。
  DevOps 有助于加快组织交付应用和服务的速度。它也使得组织更好地服务客户,以增强市场竞争力。
  DevOps 也可以被定义成开发和 IT 运维更好地沟通和协作的序列。
  DevOps 已经成为企业或组织最具价值的业务准则之一。在 DevOps 的帮助下,应用交付的质量和速度已经得到了极大的改善。
  DevOps 只是使“开发人员”和“运营人员”一起工作的一种实践或方法。DevOps 代表着 IT 文化的一种变化,它完全专注于在面向系统方法的上下文中通过采用敏捷实践来快速交付IT服务。
  DevOps 就是关于运营和开发流程的集成。 已采用DevOps的组织注意到,软件质量提高了22%,应用程序部署频率提高了17%,客户满意度提高了22%。 成功实施 DevOps 后,收入增长了19%。

为什么需要 DevOps?

  接下来,我们需要了解为什么我们需要 DevOps 而不是其他方法。

  • 运营和开发团队完全孤立地工作。
  • 在设计-构建之后,分别进行测试和部署。 这就使得他们比实际构建周期花费更多时间。
  • 在不使用 DevOps 的情况下,团队成员花费大量时间在设计,测试和部署上,而不是构建项目。
  • 手动代码部署会导致生产中的人为错误。
  • 编码团队和操作团队有各自的时间表、并且不同步,从而导致进一步的延迟。

    DevOps 历史

  • 2009年,第一届名为 DevOpsdays 的会议在比利时根特举行。 比利时顾问和 Patrick Debois 共同创立了此次会议。
  • 2012年,Puppet 的 Alanna Brown 提出并构思了 DevOps 状态报告。
  • 2014年,Nicole Forsgren、Jez Humble、Gene Kim 等人发布了年度 DevOps 状态报告。他们发现,DevOps 的采用也在 2014 年加速发展。
  • 2015年,妮可·福斯格伦(Nicole Forsgren)、吉恩·金(Gene Kim)和杰兹·汉布尔(Jez Humble)创立了 DORA(DevOps研究与任务)。
  • 2017年,妮可·福斯格伦(Nicole Forsgren),吉恩·金(Gene Kim)和杰兹·汉布尔(Jez Humble)发表了“加速:建立和扩展高性能技术组织”。

    DevOps 架构特性

      以下是 DevOps 架构的一些关键功能,例如:

1)自动化

  自动化可以减少时间消耗,尤其是在测试和部署阶段。 生产率提高了,并且自动化使发布更快。 这将导致迅速捕获错误,因此可以轻松修复它。 对于持续交付,每个代码都是通过自动化测试,基于云的服务和构建来定义的。 可以使用自动部署来促进生产。

2) 协作

  开发和运营团队作为 DevOps 团队进行协作,随着团队生产力的提高,生产力不断提高,从而改善了文化模型、增强了责任感和所有权。 这些团队分担责任并紧密同步工作,进而加快了生产部署速度。

3)集成

  应用程序需要与环境中的其他组件集成。 集成阶段是将现有代码与新功能结合起来,然后进行测试。 持续的集成和测试可以实现持续的开发。 发布和微服务的频率导致重大的运营挑战。 为了克服这些问题,就要实施持续集成和持续交付,以便以更快,更安全和可靠的方式交付。

4)配置管理

  配置管理确保应用程序仅与那些与其运行环境有关的资源进行交互。 在将应用程序的外部配置与源代码分开的情况下,不会创建配置文件。 配置文件可以在部署过程中编写,也可以在运行时加载,具体取决于运行环境。

DevOps 的优点和缺点

  以下是DevOps对业务可能具有的一些优点和缺点,例如:优点

  • DevOps 是快速开发和部署应用程序的绝佳方法。
  • 对市场变化做出更快的响应,以改善业务增长。
  • DevOps通过减少软件交付时间和运输成本来提升业务利润。
  • DevOps 清除了描述过程,从而使产品开发和交付更加清晰。
  • 改善了客户体验和满意度
  • DevOps 简化了协作,并将所有工具都放置在云中供客户访问。
  • DevOps 意味着集体责任,可以提高团队参与度和生产力。

缺点

  • DevOps 专业人士或专家的开发人员较少。
  • 使用 DevOps 进行开发非常昂贵。
  • 行业在短时间内很难采用新的DevOps技术。
  • 在自动化项目的持续集成中,缺乏DevOps知识可能是一个问题。

前提条件

  要学习 DevOps,您应该具有 Linux 的基本知识和至少一种脚本语言。

受众

  我们的 DevOps 教程旨在帮助初学者和专业人士。

软件架构指南

【注】本文节译自: Software Architecture Guide (martinfowler.com)
  当软件行业的人们谈论“架构”时,他们指的是软件系统内部设计最重要方面的一个模糊定义概念。好的架构很重要,否则将来增加新功能会变得越来越慢,而且成本更高。

  像软件世界中的许多人一样,我一直对“架构”一词持谨慎态度,因为它通常暗示着与编程的分离和不健康的浮夸。但是,我通过强调好的架构可以支持其自身的发展,并与编程紧密地交织在一起,来解决我的担忧。我的职业生涯大部分时间都围绕着以下问题:好的架构是什么样的,团队如何创建它,以及如何最好地在我们的开发组织中培养架构思维。该页面概述了我对软件架构的看法,并在这个网站上有关为你带来更多关于架构的材料。martinfowler.com 上有关软件体系结构的材料指南。
马丁·福勒
2019年8月1日

什么是架构?

  软件界的人们长期以来一直在争论架构的定义。对于某些人来说,这就像是系统的基本组织,或者是将最高级别的组件连接在一起的方式。 我对此的想法是由与拉尔夫·约翰逊(Ralph Johnson)进行的电子邮件交流形成的,后者对这一措辞提出了质疑,认为没有客观的方法来定义基础知识或高级知识,而对架构的一个更好的视角是专家开发人员达成共识的系统设计
QCon拉尔夫·约翰逊(Ralph Johnson)在 QCon 上演讲
  架构的第二种常见定义方式是“需要在项目早期就做出设计决策”,但拉尔夫也对此表示抱怨,说这更像是你希望能够在项目的早期就做出正确的决策
  他的结论是“架构是关于重要的东西,不管是什么。” 乍一看,这听起来很老套,但我发现它带有很丰富的内涵。 这意味着从结构的角度思考软件的的核心是确定重要的东西(即什么是架构),然后花精力保持那些架构元素处于良好状态。对于要成为架构师的开发人员来说,他们需要能够识别哪些要素很重要,以及哪些元素在被控制的情况下可能会导致严重的问题。
拉尔夫的电子邮件构成了我在IEEE软件专栏的核心,该专栏讨论了软件架构的含义和架构师的角色。

为什么架构很重要?

  对于软件产品的客户和用户而言,架构是一个棘手的问题-因为这不是他们能马上感知的东西。但是,糟糕的架构是促成杂乱无章的主要因素,杂乱无章是软件的要素,阻碍了开发人员理解软件的能力。包含大量附加内容的软件难以修改,导致功能实现的速度更慢,缺陷也很多。

  这种情况与我们通常的经验背道而驰。 我们习惯了“高品质”的东西看作是价格更高的东西。对于软件的某些方面(比如用户体验),这可能是正确的。但是当涉及到架构和内部质量的其他方面时,这种关系就反过来了。高的内部质量可以更快地交付新功能,因为减少了麻烦。
  虽然我们可以在短期内牺牲质量来换取更快的交货速度,但在货物堆积产生影响之前,人们低估了货物堆积导致整体交货速度较慢的速度。虽然这不是可以客观衡量的东西,但是经验丰富的开发人员认为,关注内部质量只需要几周而不是几个月就可获得回报
在2015年的OSCON上,我作了简短的演讲(14分钟),内容涉及什么是架构及其重要性。

应用架构

  软件开发中的重要决策会随着我们所考虑的上下文规模而变化。常见的规模是应用程序的规模,因此是“应用程序架构”。
  定义应用架构的第一个问题是对应用是什么没有明确的定义。我认为应用是一种社会结构:

  • 被开发人员视为一个单元的代码体
  • 业务客户将其视为一个单元的一组功能
  • 那些有钱的人将其视为单一预算

  如此宽松的定义导致应用的潜在规模很多,开发团队的人数从几人到几百人不等。(您会注意到,我认为规模是涉及的人员数量,我认为这是衡量此类事情的最有用方法。)此架构与企业架构之间的主要区别在于,围绕社会构建有一个重要程度的统一目标。

应用边界

  软件开发中尚未解决的问题之一就是确定软件的边界是什么。(浏览器是不是操作系统的一部分?)面向服务体系结构的许多支持者认为应用将不复存在-因此,未来的企业软件开发将致力于将服务组装在一起。
  我不认为应用的消失和应用界限难以划分的原则一样的。本质上,应用是一种社会结构:
马丁 · 福勒
2003.9.11

微服务指南

微服务架构模式是一种将单个应用程序开发为一组小服务的方法,每个小服务都在自己的进程中运行并与轻量级机制(通常是HTTP资源API)进行通信。这些服务围绕业务功能构建,并且可以由完全自动的部署机制独立部署。这些服务可以用不同的编程语言编写,使用不同的数据存储技术,对这些服务可进行最基本的集中管理。尽管它们的优势使它们在最近几年非常流行,但它们却伴随着分销增加、一致性降低和需要成熟的经营管理的代价。
马丁·福勒

Serverless 架构

无服务器架构是结合第三方“后端即服务”(BaaS)服务和/或包含在“功能即服务”(FaaS)平台上的托管临时容器中运行的自定义代码的应用设计。通过使用这些思想以及诸如单页应用程序之类的相关思想,这样的架构消除了对传统的永远在线服务器组件的大量需求。无服务器架构可能会受益于显着降低的运营成本、复杂性和工程交货时间,但代价是增加对于供应商的依赖性和相对不成熟的支持服务的依赖。
迈克·罗伯茨
2018.5.22

微前端

好的前端开发很难。扩展前端开发,使许多团队可以同时从事大型复杂产品开发则更加困难。在本文中,我们将描述最近的一种趋势,即将前端整体拆分成许多更小、更易于管理的部分,以及这种体系结构如何提高处理前端代码的团队效率。除了讨论各种收益和成本外,我们还将介绍一些可用的实现选项,并且将深入研究一个演示该技术的完整示例应用。
卡姆·杰克逊
2019.6.19

GUI 架构

在 21 世纪中期,我一直在从事一些写作项目,这些项目本可以成书,但尚未完成。一个是关于用户界面的架构。作为这项工作的一部分,我起草了一份关于GUI架构演变的描述,比较了表单和控件的默认方法和模型-视图-控制器(MVC)模式。MVC 是软件世界中最难理解的模式之一,这是可以理解的,因为它没有很好的文档记录。因此,我在这里的写作试图更好地了解MVC的真正含义以及它如何通过模型-视图-表示器(Model-View-Presenter)和其他形式发展起来的。
2006.7.18

展现域数据屋

模块化一个信息丰富的程序的一种最常见方法是将其分为三层:展现(UI),域逻辑(即业务逻辑)和数据访问。因此,你经常会看到Web 应用程序被划分为Web 层(了解如何处理 HTTP 请求和呈现 HTML)、业务逻辑层(包含验证和计算),数据访问层(整理如何管理数据库或远程服务中的持久性数据) 。
马丁·福勒
2015.8.26

企业架构

  应用架构集中于某种形式的概念性应用边界内的体系结构,而企业架构看起来是跨大型企业的体系结构。这样的组织通常太大了,无法将其所有软件按任何一种有凝聚的方式进行分组,因此需要跨多个具有相互独立开发的代码库的团队进行协调,并需要资金和用户彼此独立运作。
  企业架构的大部分内容都是关于了解什么是值得集中协调的成本,以及协调应采取什么形式。一个极端是中央架构小组,它必须批准企业中每个软件系统的所有架构决策。这样的小组减慢了决策的速度,无法真正理解如此广泛的系统组合中的问题,从而导致决策不力。但是另一个极端是根本没有协调,这导致团队重复工作,不同系统无法进行互操作以及团队之间缺乏技能开发和交叉学习。
  像大多数具有敏捷心态的人一样,我更喜欢在分散化方面犯错,因此会更趋于混乱,而不是令人窒息的控制。但是,站在渠道的那一边仍然意味着我们必须避开险阻,并以要最小化实际成本的方式最大化本地决策。

企业架构师加入团队

企业架构组经常远离日常开发。这可能会导致他们对开发工作的了解过时,抱怨开发团队没有从整个公司的角度出发。我的同事(ThoughtWorks CTO)Rebecca 经常看到这种情况发生,她认为加入开发团队可以提高企业架构师的效率。
瑞贝卡·帕森斯(Rebecca Parsons)
2005.9

企业架构师在精益企业中的角色

当组织采取敏捷的思维方式时,企业架构不会消失,但是企业架构师的角色会发生变化。 企业架构师不再做出选择,而是帮助其他人做出正确的选择,然后传播这些信息。企业架构师仍然需要形成愿景,然后需要在团队之间建立桥梁,以构建学习社区。这将允许团队与企业架构师作为合作伙伴,在发展中探索新方法并相互学习。
凯文·希基
2015.11.30

产品超越项目

软件项目是一种流行的资助和组织软件开发的方式。他们将工作组织成临时的、只负责构建的团队,并由业务案例中预计的特定收益提供资金。产品模式使用持久的,由构思-构建-运行的团队来解决持续存在的业务问题。产品模式使团队能够快速重新定位,减少端到端的周期时间,并允许通过使用短周期迭代来验证实际收益,同时保持软件体系结构的完整性,以保持其长期有效性。
斯里拉姆·纳拉扬(Sriram Narayan)
2018.2.20

建筑师电梯-参观高层

许多大型组织都将其 IT 引擎与行政顶层公寓分隔开许多层,这也将业务和数字战略与执行它的重要工作区分开来。 架构师的主要作用是乘坐顶层公寓和机房之间的电梯,在任何需要的地方停下来支持这些数字工作:自动化软件制造、最小化前期决策并在技术发展的同时影响组织。
格雷戈尔·霍佩(Gregor Hohpe)
2017.5.24

使用 REST 进行企业集成

大多数内部 EST API 是为单个集成点构建的一次性API。在本文中,我将讨论非公共 API 所具有的约束和灵活性,以及在多个团队之间进行大规模 RESTful 集成的经验教训。
布兰登·拜尔斯(Brandon Byars)
2013.11.18

ELK 教程 – 高效发现、分析和可视化你的数据

【注】本文译自:https://www.edureka.co/blog/elk-stack-tutorial/

  随着越来越多的 IT 基础设施转身云计算,对公共云安全工具和日志分析平台的需求也在迅速增加。不管组织的规模有多大,每天都会生成大量的数据。这些数据中有相当一部分是由公司的 Web 服务器日志组成的。日志是最重要的信息来源之一, 但往往被忽视。每个日志文件都包含一些宝贵的信息,这些信息大多是非结构化的,没有任何意义。如果不对此日志数据进行详尽的分析,那么企业可能会忽视其周围的机会和威胁。这是日志分析工具非常有用的地方。ELK Stack 或 Elastic Stack 是完整的日志分析解决方案,它有助于深入搜索、分析和可视化不同机器生成的日志。通过本教程,我将为您提供相关见解。
  首先,让我们列出要讨论的主题:

  • 什么是 ELK Stack?
  • ELK Stack 架构
  • ELK Stack 安装
  • Elasticsearch 教程
  • Logstash 教程
  • Kibana 教程

  本教程将帮助您一起理解 Elasticsearch、Logstash 和 Kibana 的基础知识,并帮助您在 ELK Stack 中打下坚实的基础。
  首先让我们来了解什么是 ELK Stack。

什么是 ELK Stack?


  众所周知的 ELK Stack 最近被更名为 Elastic Stack。它是三个开源工具的强大集合:Elasticsearch、Logstash 和 Kibana。
  这三种不同的产品最常一起用于不同 IT 环境中的日志分析。使用 ELK Stack,您可以执行集中式日志记录,这有助于识别 Web 服务器或应用程序的问题。它使您可以在一个地方搜索所有日志,并通过在特定时间范围内关联多个服务器的日志来识别跨多个服务器的问题。
  现在让我们详细讨论这些工具。

Logstash

Logstash 是数据收集管道工具。它是 ELK Stack 的第一个组件,它收集数据输入并将其输入到 Elasticsearch。它可以一次从不同来源收集各种类型的数据,并立即提供以备将来使用。

Elasticsearch

Elasticsearch 是基于 Lucene 搜索引擎的 NoSQL 数据库,并使用 RESTful API 构建。它是一个高度灵活的分布式搜索和分析引擎。此外,它通过水平可伸缩性提供了简单的部署、最大的可靠性和易于管理的功能。它提供高级查询以执行详细分析,并集中存储所有数据以快速搜索文档。

Kibana

Kibana 是一种数据可视化工具。它用于可视化 Elasticsearch 文档,并帮助开发人员立即对其进行深入了解。Kibana 仪表板提供了各种交互式图表、地理空间数据、时间线和图表,以可视化使用 Elasticsearch 完成的复杂查询。使用 Kibana,您可以根据自己的特定需求创建和保存自定义图形。
  下一部分将讨论 ELK Stack 架构以及其中的数据流向。

ELK Stack 架构

  以下是 ELK Stack 的架构,显示了 ELK 中日志流的正确顺序。在此,Logstash 会根据提供的过滤条件来收集和处理从各种来源生成的日志。然后,Logstash 将这些日志通过管道传输到 Elasticsearch,然后 Elasticsearch 分析和搜索数据。最后,使用 Kibana,可以根据要求对日志进行可视化和管理。

ELK Stack 安装

第 I 步:打开 https://www.elastic.co/downloads

第 II 步:选择并下载 Elasticsearch。
第 III 步:选择并下载 Kibana。
第 IV 步:选择并下载 Logstash。
第 V 步:解压缩所有三个文件以获取对应文件夹的文件。

安装 Elasticsearch

第 VI 步:现在打开 elasticsearch 文件夹并转到 bin 文件夹
第 VII 步:双击 elasticsearch.bat 文件以启动 elasticsearch 服务器。

第 VIII 步:等待 elasticsearch 服务器启动。
第 IX 步:要检查服务器是否已启动,请转到浏览器并键入 localhost:9200

安装 Kibana

第 X 步:现在打开 kibana 文件夹 并转到 bin 文件夹
第 XI 步:双击 kibana.bat 文件以启动 kibana 服务器。

第 XII 步:等待 kibana 服务器启动。
第 XIII 步:要检查服务器是否已启动,请转到浏览器并键入 localhost:5601

安装 Logstash

第 XIV 步:现在打开 logstash 文件夹
第 XV 步:要测试您的 logstash 安装,请打开命令提示符,然后转到 logstash 文件夹。现在输入:

binlogstash -e 'input { stdin { } } output { stdout {} }'

第 XVI 步:等待,直到命令提示符上显示“ Pipeline main started”出现在命令提示符下。

第 XVII 步:现在,在命令提示符下输入一条消息,然后按 Enter 键。
第 XVIII 步:Logstash 将时间戳和 IP 地址信息附加到消息中,并将其显示在命令提示符下。
  既然我们完成了安装,那么现在让我们更深入地研究这些工具。让我们从 Elasticsearch 开始。

Elasticsearch

  如前所述,Elasticsearch 是一个高度可扩展的搜索引擎,它运行在基于 Java 的 Lucene 引擎之上。它基本上是一个 NoSQL 数据库。这意味着它将以非结构化格式存储数据,并且无法对任何类型的交易执行 SQL 查询。换句话说,它将数据存储在文档中,而不是表和模式中。为了获得更好的图像,请检查下表,该表显示了与数据库相比在 Elasticsearch 中的内容。

  现在让我们熟悉 Elasticsearch 的基本概念。
使用Elasticsearch时,需要遵循三个主要步骤:

  1. 索引
  2. 映射
  3. 搜索

  让我们一个一个详细地谈谈。

索引

  索引编制是添加数据 Elasticsearch 的过程。之所以称为“索引”,是因为将数据输入到 Elasticsearch 中后,它将被放入 Apache Lucene 索引中。然后,Elasticsearch 使用这些 Lucene 索引来存储和检索数据。索引编制与 CRUD 操作的创建和更新过程相似。   索引方案由 名称/类型/id 组成,其中名称和类型是必填字段。 如果您不提供任何 ID,Elasticsearch 将自己提供一个 ID。 然后,将整个查询附加到 HTTP PUT 请求中,最终 URL 如下:PUT name/type/id 与 HTTP 有效负载一起,还将发送包含字段和值的 JSON 文档。
  以下是创建一个美国客户的文档的示例,该文档及其字段中的详细信息。

PUT /customer/US/1 
{
    "ID": 101,
    "FName": "James",
    "LName": "Butt",
    "Email": "jbutt@gmail.com",
    "City": "New Orleans",
    "Type": "VIP"
}

  它会给你以下输出:

  这里显示文档已创建并添加到索引中。
  现在,如果您尝试在不更改标识的情况下更改字段详细信息,Elasticsearch 将使用当前详细信息覆盖现有文档。

PUT /customer/US/1
{
    "ID": 101,
    "FName": "James",
    "LName": "Butt",
    "Email": "jbutt@yahoo.com",
    "City": "Los Angeles",
    "Type": "VVIP"
}


  这里显示文档已经更新了索引的新细节。

映射

  映射是设置索引模式的过程。通过映射,您可以告诉 Elasticsearch 你的模式中属性的数据类型。如果在预索引时未针对特定对象进行映射,则 Elasticsearch 将动态地将泛型类型添加到该字段。但是这些泛型类型是非常基本的,大多数时候都不能满足查询的期望。
  现在让我们尝试映射查询。

PUT /customer/
{
    "mappings": {
        "US": {
            "properties": {
                "ID": {
                    "type": "long"
                },
                "FName": {
                    "type": "text"
                },
                "LName": {
                    "type": "text"
                },
                "Email": {
                    "type": "text"
                },
                "City": {
                    "type": "text"
                },
                "Type": {
                    "type": "text"
                }
            }
        }
    }
}


  当您执行查询时,您将获得这种类型的输出。

搜索

  具有特定索引和类型的一般搜索查询如下:

POST index/type/_search

  现在,让我们尝试搜索“customer”索引中存在的所有客户的详细信息。

POST /customer/US/_search

  当您执行此查询时,将生成以下结果:

  但是,当您要搜索特定结果时,Elasticsearch 提供了三种方法:

使用查询

  使用查询,您可以搜索一些特定的文档或条目。例如,让我们对属于“ VVIP”类别的客户执行搜索查询。

POST /customer/US/_search
{
    "query": {
        "match": {
            "Type": "VVIP"
        }
    }
}

使用过滤器

  使用过滤器,您可以进一步缩小搜索范围。以下是搜索 ID 为“ 101”的 VVIP 客户的示例:

POST /customer/_search
{
    "query": {
        "match": {
            "Type": "VVIP"
        }
    },
    "post_filter": {
        "match": {
            "ID": 101
        }
    }
}

  如果执行此查询,则会得到以下结果:

使用聚合

  聚合是一个框架,可帮助通过搜索查询聚合数据。小型聚合可以结合在一起,以构建所提供数据的复杂摘要。让我们执行一个简单的汇总,以检查索引中有多少类型的客户:

POST /customer/_search
{
    "size": 0,
    "aggs": {
        "Cust_Types": {
            "terms": {
                "field": "Type.keyword"
            }
        }
    }
}


  现在让我们看看如何从索引中检索数据集。

获取数据

  要检查索引中包含的文档列表,您只需要发送以下格式的 HTTP GET 请求:

GET index/type/id

  让我们尝试检索“ id”等于 2 的客户的详细信息:

GET /customer/US/2

  成功执行后,它将为您提供以下类型的结果。

  使用 Elasticsearch,您不仅可以浏览数据,还可以删除或删除文档。

删除数据

  使用删除约定,您可以轻松地从索引中删除不需要的数据并释放内存空间。要删除任何文档,您需要以以下格式发送 HTTP DELETE 请求:

DELETE index/type/id.

  现在让我们尝试删除 ID 为 2 的客户的详细信息。

DELETE /customer/US/2

  执行此查询时,您将获得以下类型的结果。

  至此,我们讲解了使用 Elasticsearch 的 CRUD 操作的基础知识,了解这些基本操作将帮助您执行不同类型的搜索。
  现在让我们开始学习 ELK Stack 的下一个工具 Logstash。

Logstash

  正如我已经讨论的那样,Logstash 是一种管道工具,通常用于收集和转发日志或事件。它是一个开源数据收集引擎,可以动态集成来自各种来源的数据并将其标准化到指定的目标位置。

  使用多个输入,过滤器和输出插件,Logstash 可以轻松转换各种事件。至少,Logstash 需要在其配置文件中指定的输入和输出插件来执行转换。以下是 Logstash 配置文件的结构:

input {
    ...
}

filter {
    ...
}

output {
    ...
}

  如您所见,整个配置文件分为三个部分,每个部分都包含一个或多个插件的配置选项。这三个部分是:

  1. input(输入)
  2. filter (过滤)
  3. output (输出)

  您也可以在配置文件中应用多个过滤器。在这种情况下,其应用程序顺序将与配置文件中的规范顺序相同。
  现在,让我们尝试配置 CSV 文件格式的美国客户数据集文件。

    file {
        path => "E:/ELK/data/US_Customer_List.csv"
        start_position => "beginning"
        sincedb_path => "/dev/null"
    }
}
filter {
    csv {
        separator => ","
        columns => ["Cust_ID", "Cust_Fname", "Cust_Lname", "Cust_Email", "Cust_City", "Cust_Type"]
    }
    mutate {
        convert => ["Cust_ID", "integer"]
    }
}
output {
    elasticsearch {
        hosts => "localhost"
        index => "customers"
        document_type => "US_Based_Cust"
    }
    stdout {}
}

  要将这个 CSV 文件数据插入 elasticsearch 中,您必须通知 Logstash 服务器。
  为此,请执行以下步骤:

  1. 打开命令提示符
  2. 进入 Logstash 的 bin 目录
  3. 输入:logstash –f X:/foldername/config_filename.config 然后按回车。一旦您的 logstash 服务器启动并运行,它将开始将文件中的数据传输到Elasticsearch 中。

      如果要检查是否成功插入了数据,请转到 Sense 插件并键入:GET /customers/
      它会为您提供已创建的文档数。
      现在,如果要可视化此数据,则必须使用 ELK Stack 的最后一个工具,即Kibana。因此,在本教程的下一部分中,我将讨论 Kibana 及其使用方式,以可视化您的数据。

    Kibana

      如前所述,Kibana 是一个开源的可视化和分析工具。它有助于可视化 Logstash 管道传输并存储到 Elasticsearch 中的数据。您可以使用 Kibana 来搜索,查看此存储的数据并与之交互,然后在各种图表,表格和地图中对其进行可视化。Kibana 的基于浏览器的界面简化了海量数据并反映了 Elasticsearch 查询中的实时变化。此外,您还可以轻松创建、自定义、保存和共享仪表板。
      一旦您了解了如何与 Elasticsearch 和 Logstash 一起使用,学习 Kibana 就不是什么大事了。在本教程的这一部分,我将向您介绍为了对数据进行分析所需的各种功能。

    管理页面

      在这里,您必须执行 Kibana 的运行时配置。 在此页面中,您需要指定一些搜索内容。请参见以下示例,在该示例中,我已经配置了“customer”索引的条目。

      如您所见,在“索引模式(Index Patterns)”字段中,您需要指定要使用的索引。确保在“时间过滤器字段名称”中将其选择为@timestamp。然后,您可以继续并单击创建以创建索引。如果索引创建成功,您将看到以下页面类型:

      在这里,您可以根据需要从下拉列表中选择不同的过滤器。此外,要释放内存,您还可以删除特定的索引。

    发现页面

      通过“发现”页面,您可以访问存在于每个与所选索引模式匹配的每个索引的文档。 您可以轻松地交互和浏览Kibana服务器上存在的所有数据。 此外,您可以查看文档中存在的数据并对其进行搜索查询。 下面你可以看到,我正在搜索来自“洛杉矶”的“ VIP”客户。
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ANoSab7B-1619606511268)(https://d1jnx9ba8s6j9r.cloudfront.net/blog/wp-content/uploads/2017/11/28-1.png)]
      因此,如您所见,我们只有一位来自洛杉矶的VIP客户。

    可视化页面

      可视化页面使您可以可视化以图表、条形图、饼图等形式显示在 Elasticsearch 索引中的数据。您甚至可以在此处构建仪表板,这些仪表板将基于 Elasticsearch查询显示相关的可视化效果。通常,使用一系列 Elasticsearch 聚合查询来提取和处理数据。当您转到“可视化”页面并搜索保存的可视化时,或者可以创建一个新的可视化。

      您可以以任何形式汇总数据。为了方便用户,提供了不同类型的可视化选项。

      让我向您展示如何根据用户类型可视化美国客户数据。

      要执行可视化,请按照以下步骤操作:

  4. 选择可视化类型。 [这里我用的是饼图]
  5. 在汇总字段中,从下拉列表中选择“术语(term)”。
  6. 在“字段(field)”中,选择要执行搜索的字段类型。
  7. 您还可以指定可视化的顺序和大小。
  8. 现在单击执行按钮以生成饼图。

    仪表盘页面

      “仪表板”页面显示已保存的可视化的集合。在这里,您可以添加新的可视化效果,也可以使用任何保存的可视化效果。

    Timelion 页面

      Timelion 是一个时间序列数据可视化工具,它将完全独立的数据源整合到一个界面中。 它由一种单行表达语言驱动,可用于检索时间序列数据,执行计算以简化复杂问题并可视化结果。

    开发工具页面

      Kibana 的“开发工具”页面包含诸如“ Beta Sense”插件之类的开发工具,可用于与 Elasticsearch 中存在的数据进行交互。它通常被称为 Kibana 的控制台。以下是一个示例,其中我使用了 Kibana 的 Sense 插件来搜索类型为“ US_based_cust”的“客户(customers)”索引:
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s4xVhcR2-1619606511273)(https://d1jnx9ba8s6j9r.cloudfront.net/blog/wp-content/uploads/2017/11/33-1.png)]
      本文到此结束。现在,您可以使用 Logstash、Elasticsearch 和 Kibana 对任何数据执行各种搜索和分析。

三大业界大佬的DevOps解决方案

  DevOps 在商业界的一些解决方案,主要包括:

  1. 微软公司的 Azure DevOps
  2. 亚马逊公司的 Aws DevOps
  3. 阿里云的 云效 DevOps

Azure DevOps


  Azure DevOps 也称为 Microsoft Visual Studio 团队服务(VSTS)。它是为云构建的一组协作开发工具。 VSTS 通常被用作独立术语,Azure DevOps 是一个由几种不同产品组成的平台,例如:

  • Azure 测试计划
  • Azure 看板
  • Azure 存储库
  • Azure 流水线
  • Azure 制品

  Azure DevOps是将创意转化为工作软件所需的一切。您可以使用 Azure 工具计划项目。

  Azure 流水线是 Azure DevOps 的 CI 组件。Azure 流水线是微软的云原生连续集成服务器,它使团队可以从云中连续构建、测试和部署所有组件。Azure 流水线可以连接到任意数量的源代码存储库,例如 Azure Repos、GitHub、Tests,以获取代码和制品以进行应用交付。

Azure DevOps 服务器

  Azure DevOps Server 是微软的一个产品,提供版本控制、需求管理、报告、软件库管理、项目管理、测试、自动生成和发布管理功能。它涵盖了应用的整个生命周期,并启用了 DevOps 功能。
  Azure DevOps 可以用作众多集成开发环境的后端,但针对 Microsoft Visual Studio 和 Eclipse 的所有平台上进行了定制。

Azure DevOps 服务

  微软宣布在 Microsoft Azure 平台上发布该软件即 Visual Studio 的服务产品,当时微软将其称为在线Visual Studio。

  微软为 Azure DevOps 服务提供了 Visual Studio,基本和利益干系人访问级别。基本计划是免费的,最多可容纳五个用户。订阅了 Visual Studio 的用户可以免费添加到项目中。

AWS DevOps

  AWS 是最好的云服务提供商,DevOps 是软件开发生命周期的实现。

  以下是使 AWS DevOps 成为非常受欢迎的组合的一些原因,例如:

  • AWS CloudFormation
  • AWS EC2
  • AWS CloudWatch
  • AWS CodePipeline

  让我们简要地看一下这些,例如:

AWS CloudFormation


  与开发团队相比,DevOps 团队需要更频繁地创建和发布云实例和服务。诸如 EC2 实例,ECS 容器和 S3 存储桶之类的 AWS 资源模板使您可以设置整个堆栈,而不必将所有内容放在一起。

AWS EC2

  您可以在 EC2 实例中运行容器。因此,您可以利用 AWS 安全和管理功能。

  Amazon EC2 提供最广泛、最深入的实例选择,这些实例构建于最新的计算、存储和网络技术,进行了高性能和安全设计。

AWS CloudWatch


  该监控工具可跟踪 AWS 必须提供的所有资源。轻松使用第三方工具进行监视,例如sumo logic 等。

AWS CodePipeline


  代码流水线是 AWS 的一项重要功能,它极大地简化了你管理 CI / CD 工具集的方式。它与 Jenkins、GitHub 和 CodeDeploy等工具集成,使你可以直观地控制从构建到生产的应用更新流程。

云效 DevOps


  阿里云云效,云原生时代新 DevOps 平台,支持公共云、专有云和混合云多种部署形态,通过云原生新技术和研发新模式,助力创新创业和数字化转型企业快速实现研发敏捷和组织敏捷,打造“双敏”组织,实现 10 倍效能提升。

项目

一站式项目管理

  以项目为维度,管理需求、任务、缺陷、迭代、里程碑、风险和文档。大型项目管理
  支持父子项目以分级管理战略专题项目,支持项目集合以联合管理双十一等大型协作项目。

快速迭代项目管理

  支持Scrum、看板等多种敏捷实践。

应用

应用全生命周期管理

  以应用为单位,申请、创建和配置软件运行所需资源、环境和中间件服务,提供变更、发布、监控和运维等应用全生命周期管理服务。

多种部署运行方式

  支持Docker等多种部署运行方式

交付

持续交付流

  多人协作开发集成、多种编程语言构建打包和分布式集群部署发布等服务。

质量与安全保障

  轻松搭建从代码提交、集成、构建到测试环境、预发环境、线上环境部署发布验证的持续交付流水线,质量和安全层层把关。

运营

用户反馈跟踪

  用户反馈秒级必答,产品知识智能回复。

产品动态监测

  问卷调查和舆情监测,把握产品动态。

数据驱动优化

  分析研发和运营数据,数据驱动效能提升和产品优化创新。

面向服务体系结构的领域驱动设计

【注】本文译自:https://www.thoughtworks.com/insights/blog/domain-driven-design-services-architecture
  这篇文章是关于软件设计的选择。特别是大型系统,这些系统可能会以服务端点的形式分为多个可部署的对象。我不会特别谈论服务端点设计,但是我想讨论创建多个服务应用的构思阶段。

  当我们面对复杂的问题时,我们通常试图理解复杂的单个部分。通过分解问题,我们将其变成为更易于理解和管理的部分。
  正如在许多产品/项目管理周期中所描述的,对于现实生活中的问题,这通常是由本能驱动的。 我们不会使用公式来了解去一个需要签证的国家需要做什么。我们知道我们需要签证才能旅行,我们慢慢掌握需要的文件文件,需要填写哪些表格以及如何填写这些表格。当我们执行其中一个步骤时,我们不会将流程的所有细节都牢记在心,而只是要做手头的任务。这与要完成的任务的大小有关。潜在的真实标准是关于时间或进度、我们的执行力、我们的认知能力及其与任务熟悉程度的关系,甚至可能是执行这些任务的物理位置( 领事馆与 Photoshop 等)。
  在软件开发领域并没有什么不同。多年来,瀑布式的配方已被应用到软件开发过程中,最终,主要是基于启发式和基于经验的评估技术(计划扑克、T – 恤尺寸)和敏捷过程。在现实生活中,我们试着不去详述整个过程,而是通过观察我们的最新表现来尝试和理解整个旅程。
  同样适用于我们针对问题建模的软件。我们开始将它们分解为不同的应用的是方便管理单个应用,以更少的依赖关系更快地开发和部署,最后带来更多的技术选择自由。我们意识到,我们无法制定出适合所有人的完整流程。我们着眼于各个部分,并认识到我们在设计模式或技术方面的集体经验,并尝试应用其中最好的选择。
  理解和解决复杂性的一个有趣的软件设计技术是领域驱动设计(DDD)。领域驱动设计提倡基于与我们的用例相关的业务现实进行建模。由于 DDD 方法现在已经过时,而且宣传水平正在下降,我们许多人都忘记了 DDD 方法确实有助于理解手头的问题,并朝着对解决方案的普遍理解来设计软件。在构建应用DDD 会以域和子域的形式讨论的问题。它将问题的独立步骤/领域描述为边界上下文,强调使用一种通用语言来讨论这些,并添加了许多技术概念,如实体、值对象和聚合根规则以支持实现。有时,这些技术规则被视为是实施 DDD 的硬障碍,但最终,人们往往会忘记重要的部分是根据业务问题组织代码工件,并使用与(1)相同的通用语言。

设计为服务应用的边界上下文

  我想谈论的架构风格与微服务非常相似。它是关于将单体应用分离为多个独立的服务应用,或者从一开始就在边界上下文(DDD概念)的帮助下单独开发它们。
  有许多资源都强调了微服务叙述中更细粒度的服务的优点。越来越多的文章、博客和其他内容是关于陷阱和在向细粒度服务过渡之前或过渡期间应该拥有的安全网的。我将尽量不重复微服务或其他支持元素的好处,这些是迁移到这样的体系结构中所需要。我不想强调结果服务的“小型”性质,而是想强调如何通过应用领域驱动的设计概念来更好地分离这些服务。
  让我们使用一个真实的示例来实现我们的想法-借记卡/信用卡获取域。这个领域可以(不幸的是,很多时候都是这样)作为一组单体应用来实现。我们拥有多个应用的唯一原因是由于不同的应用中的存在严格的技术限制(例如希望执行批处理)。

  我所看到的大多数成功的架构都认识到,通过数据库进行集成是一种糟糕的实践,因为它使技术应用和业务职责之间的界限变得模糊,使业务逻辑泄漏到数据库中,并通过添加更多的应用服务器来阻止水平扩展。因此,以单体应用的的服务集成的形式发展到更好的架构。

  现在,应用之间的界限更加清晰了。但是,您可以看到,仍然存在隐藏的数据库交互,这一次是在各个应用内部发生的。我称它们为隐藏的,因为通常一开始它们通常很难被注意到。随着时间的流逝,代码的纠缠将使原先分离的业务流程人为地关联起来,并在业务开发中引入更多的摩擦,因为这种共置托管需要联合部署单独的功能,这可能会减慢速度。
  如果您幸运地有一个领域模型来指导的话,则领域建模可帮助您识别和分离复杂的实现。如果您还没有现有应用的域模型(在大多数情况下通常是这样),则无需遍历代码以了解不同的职责,而是构建域模型并将功能映射到手边的应用可能是一个更好的方法。这既能节省时间,又能避免被细节淹没的风险。此外,如果业务与开发团队之间存在差距(这可能是域模型最初不存在的主要原因),那么讨论域模型并映射到现有应用的功能将有助于缩小这一差距。

  我们设计演进的下一步是将域边界分离反映到我们的架构以及边界上下文中。一个域具有多个边界上下文意味着在同一域中可以有多个服务应用运行。有了适当的域模型,潜在的分离点就更可见了,这使我们可以从潜在的更细粒度的应用中受益(诸如单独发行和版本控制的好处,具有更多功能驱动的纯服务端点的潜力等,其中大多数已经在微服务文章中进行了讨论)。尽管许多微服务讨论都围绕技术不可知论和开发规范(避免/破坏整体),但对于我们大多数人所从事的应用而言,非常有价值的一项是领域和设计方面。一旦过渡到微服务架构(借助域模型),DDD 和更细粒度的服务便可以协同工作、相互支持。

  这还将为团队提供一定程度的独立性,更完善的服务功能以及更分离的交互,如许多微服务文章所述.
  同样,从我们的示例信用卡付款获取域中可以看出,这并不是我们的服务可以做到的最细粒度的分离。相反,这是在我们的领域知识所指导下最有意义的分离。重点不是规模,而是业务能力。我相信这是“正确的 SOA”,正如许多圈子所说的那样。

面向资源的架构(ROA)概述

【注】本文译自: Overview of Resource-Oriented Architectures (ROA) | Developer.com

了解面向资源的架构 (ROA)、其价值以及最佳实践。

  面向服务的架构 (Service-Oriented Architecture,SOA) 和面向资源的架构 (Resource-Oriented Architecture,ROA) 是用于实现健壮、可扩展的分布式应用程序架构的架构设计模式。分布式架构由通过定义良好的接口在网络上使用的组件组成。在 ROA 中,这些组件被称为资源,而在 SOA 中,它们被称为服务。本文概述了面向资源的体系结构。

什么是面向资源的架构 (ROA)?

  面向资源的架构 (ROA) 是一种架构风格,它扩展了 REST 架构风格,并提供了更广泛、可扩展、灵活且与传输无关的架构。面向资源的架构 (ROA) 范式建立在资源的概念之上。资源是一个独立的、可识别的实体,其状态可以被分配一个统一的资源定位符 (URI)。服务代表所请求操作的执行,而资源代表可通过一致的标准化接口进行管理的分布式组件。
  面向资源架构的特征之一是与传输无关。因此,必须有特定的机制将面向资源的服务暴露给外部世界。当消费者请求统一资源定位器(Uniform Resource Locator,URL)并指定访问方法(例如,GET、PUT、POST 和 DELETE)时,该 URL 将转换为相对的内部 URI。访问方法被转化为动作。
  面向资源的架构仅围绕四个概念建模:

  • 资源
  • URI
  • 表述
  • 链接和连通性

  以下是面向资源架构的四个属性:

  • 可寻址性
  • 无状态性
  • 连通性
  • 统一的接口

面向资源的架构:资源

  资源是 ROA 的基石;资源是信息的逻辑表示。例如,学生是数据点的抽象集合,可以用多种方式表示,包括 XHTML、JSON 和 XML。 资源名称包括以下内容:

  • 资源的类型
  • 资源标识符
  • 父元素的资源名
  • API 服务的名称

  资源是 REST 或 RESTful 架构中最重要的信息抽象。术语“资源”是指任何可识别的信息。这些信息可以是文档、计算机、汽车、临时服务(例如“俄亥俄州今天的天气”)、其他资源的集合、个人、学生等。
  每个不可变资源表示都可以通过相对的统一资源指示符 (URI) 来标识,它可能包括到其他资源以及其他不可变资源的连接。请注意,一个资源也可以有多个 URI。
  每种资源应具有以下特征:

  • 是唯一的
  • 必须至少有一个表述
  • 具有属性,模式可以被访问并提供上下文

统一资源标识符(URI)

  统一资源标识符 (Uniform Resource Identifier,URI) 是包含资源名称和地址的字符序列,用于标识逻辑或物理资源。请注意,资源必须具有一个或多个 URI。 没有 URI,一条信息不被视为资源,因为它不能被引用或访问。 您可以使用 URI 来标识任何事物,例如现实世界的对象、网页、书籍等。
  下面是一个 URI 的例子:语法::示例:http://payroll/employee/1234 下表说明了如何指定资源的相对 URI:

  • 动作
    相对 URI
    目的
  • Read
    /student/3
    读取 ID 是 3 的学生记录
  • Delete
    /student/4
    删除 ID 是 4 的学生记录

面向资源架构ROA的特性

  以下是面向资源的架构的四个基本属性:

可寻址性

  可寻址性是面向资源架构的一个基本特征。如果应用程序将其数据集的感兴趣方面发布为服务端点,则可以认为该应用程序是可寻址的。这些服务可以反过来用于处理应用程序的数据。由于 ROA 中的资源使用 URI 公开,因此应用程序应使用 URI 公开其数据。

无状态

  无状态是面向资源架构的另一个特征,这意味着该架构中的每个 HTTP 请求都是独立发生的。换句话说,当客户端向服务器请求资源时,客户端必须提供服务器请求成功所需的所有信息。服务器从不存储来自先前请求的信息,也就是说,服务器上不存储状态信息。如果服务器需要来自较早请求的信息来处理请求,则客户端必须在另一个请求中再次发送该信息。

连通性

  在面向资源的架构中,表述是超媒体:包含数据和其他资源链接的文档。RESTful Web 服务遵循超媒体作为应用程序状态引擎 (Hypermedia As the Engine Of Application State, HATEOS) 原则。这是数据格式影响应用程序中转换状态的原则。到其他资源的链接嵌入在响应中,但对于相同的资源,它们可能会根据其当前状态而有所不同。
  网络最重要的特征之一是它的互连性。也就是说,互联网上的几乎所有信息都通过超链接链接在一起。超链接可用于将互联网上可访问的任何资源连接到另一个资源。基于 ROA 构建的应用程序应将其所有资源相互链接,反之亦然。当我们为资源选择合适的表述时,我们可以在应用程序中实现连通性。

统一接口

  您应该有一组定义良好的方法来操作应用程序中的资源。例如,HTTP 提供了以下您可能通常需要在任何应用程序中执行的常见操作:

HTTP GET – 获取一个资源

HTTP POST – 创建新资源

HTTP PUT – 修改一个存在的资源

HTTP DELETE – 删除一个存在的资源

HEAD 和 OPTIONS HTTP 方法

  下面是您应该了解的另外两个重要的 HTTP 方法:

HTTP HEAD – 用于检索资源的元数据表述

HTTP OPTIONS – 用于检查特定资源支持哪些 HTTP 方法

总结

  面向资源的体系结构是无状态的,并以资源为中心。URI 用于标识每个资源。您总是可以在计算机上同时拥有同一资源的多个副本。一个资源也可以有多个 URI。