Microservices Development with BDD and IOD

Original author: Ken Pugh
  • Transfer
BDD - development through behavior. BDD for microservices is a collaboration of the client, developers and testers. BDD is a development that takes into account both technical interests and business requirements. This approach is usually used to describe application interfaces, and since microservices are the details of the implementation of the system, BDD is also suitable for developing microservices. How to do it - in the translation of Ken Pugh.

image

About the Author: Ken Pugh teaches companies to develop flexibility, creates high-quality systems using Acceptance Test-Driven Development, BDD, DevOps acceleration. Ken has written several books on software development, was a Jolt Award 2006 Prefactoring winner, one of the creators of the SAFe Agile Software Engineering course.

Behavior in BDD is often expressed by the Given / When / Then construct . We are given a certain state when an action or event occurs, then the state changes and / or information is returned.

For example, stateless logic, such as business rules and computations, simply describes the conversion of input to output.

Interface Oriented Design uses the principle of “design for interfaces, not implementations . Consumers of the service use the interface that it provides, not the internals. This means that such an interface should be clearly thought out, including error behavior. For definition of terms in the description of the interface or its behavior it is possible to use DDD - Domain Driven Design.

Microservices can be synchronous when a consumer directly calls another service and expects a result, or asynchronous whenthe service responds to a message that the client has queued .

Consider an example of a synchronous service.

Synchronous service


Imagine a service that calculates a discount on a sales order. The whole process is a set of related operations.



The behavior of this service can be described as follows:

Get discount for a customer 
Given these inputs
Customer category 
Order Amount 
Then service outputs 
Discount Amount 

The service can calculate the discount using algorithms in the code, based on the local data database or by contacting other services.

It can use JSON or XML as a message format. However, describing the service without specifying implementation details helps to separate the semantics of operations from syntax.

What is the behavior?


Using BDD, you can start designing with sample data to get an idea of ​​the desired behavior. Service, client and tester developers can come up with this example. The first two columns are the input to the service, and the column to the right is the output.

Customer category
Order price
Discount amount?
Good
100.00 USD
1.00 USD
Excellent
100.00 USD
2.00 USD

The example shows domain terms that may require further refinement - for example, describe valid values.



It is understood that the service returns the correct result if the input data falls into the range of acceptable values.

The behavior description, especially for microservices, often includes responses in the event of failures and errors. A description of potential failures helps the consumer understand what to do in such cases. Customers of the service can use special libraries, for example, Hystrix from Netflix, to fix some of these failures.

Some potential errors of our service:

Crash
Invalid query syntax
Dependent Services Call Timeout
Invalid request parameter value

Failures can be expressed as numeric or character constants in the communication protocol.

Using meaningful names in BDD helps emphasize the semantics of a failure, not its syntax.

If the value that was passed as a category is not in the list of valid values, then the service will return a failure indicator: "Invalid value of query parameters." This can be represented, for example, by returning HTTP code 400 and the corresponding text description.

Alternatively, you can determine the discount value that will be returned, for example, 0 if any of the parameters is incorrect. In this case, the service should be responsible for recording this problem so that its consequences can be analyzed.

Service BDD tests can form a context for its unit tests. In the design process, the responsibility for passing BDD tests rests with classes and methods . Unit tests determine these responsibilities.

Stubs


When testing a service, stubs of dependent services that it calls are often required. They are especially needed for slow, expensive or random services. If the behavior of the discount service has never changed, then when testing the client, you can use the combat instance.

Change is often inevitable, so stubs are usually needed.



A stub can always return the same values, for example:

Customer category
Order price
Discount amount?
Good
100.00 USD
1.00 USD
Excellent
100.00 USD
2.00 USD

Customer tests can rely on these values. In this example, constant behavior may be sufficient. For other tests, a custom stub response is preferable.

Alternatively, the discount service stub can simply return the same amount, regardless of the input.

How can this stub fit into a wider scenario? Consider the behavior of the system for an order, which includes both a discount and a tax. The tax is calculated by the microservice, similar to the discount.



There is a buyer.

Buyer Category
Location
Good
North Carolina

Adjustable discount.

Customer Category
Order amount
Discount Amount?
Good
100.00 USD
1.00 USD

The tax is established.

Location
number
Tax?
North Carolina
99.00 USD
6.60 USD

When a customer places an order:

Order price
100.00 USD

Then order options.

Order price
a discount
Amount after discount
tax
Total payable
100 USD
1.00 USD
99.00 USD
6.60 USD
105.60 USD

Services with state


If the discount service uses the database to obtain information for calculating the discount, then its contents are the state of the service. State changes in response to data updates should be documented. Suppose the service had this state.

Customer category
Threshold level
Discount percentage
Good
100.00 USD
1%
Excellent
50,00 USD
2%

In this case, the service should allow changing this data. The update can be organized so that individual elements are updated or the whole table is updated immediately. Here is an example of a behavioral test for an individual update.

Given current data.

Customer category
Threshold level
Discount percentage
Good
100.00 USD
1%
Excellent
50,00 USD
2%

When an item is updated.

Customer category
Threshold level
Discount percentage
Excellent
50,00 USD
3.5%

Then updated data.

Customer category
Threshold level
Discount percentage
Good
100.00 USD
1%
Excellent
50,00 USD
3.5%

You can also verify that the updated data is used to calculate the discount.

Customer category
Threshold level
Discount amount?
Excellent
100.00 USD
3,50 USD

The discount service may have local storage for saving data in this example, but it may also depend on a separate storage service for this data. If so, then the tests from the previous section apply to a separate service. But every addiction adds problems. What should be the behavior of a service if its dependencies are unavailable? For a discount service, should this indicate a failure or should it simply return the default value, the same 0? Sometimes you can use global error handling policies, but  often the decision depends on the context of the service .

Test Formulation and Automation


Once the behavior of the microservice is consistent, it can be formulated as automated tests. There are several microservice testing systems, such as PACT or Karate. In addition, you can use BDD frameworks such as Cucumber or FIT.

For example, Cucumber uses libraries to query services. Then additional information about the environment can be presented as part of the script.

For example, a Cucumber object file may include.

Scenario: Compute discount for an order amount
Given setup is:
| URL | myrestservice.com |
When discount computed with:
| Method | GET |
| Path | discount |
| Version | 1 |
Then results for each instance are:
| Customer Category | Order Amount | Discount Amount? |
| Good | 100.00 USD | 1.00 USD |
| Excellent | 100.00 USD | 2.00 USD |

The step options depend on your testing conventions.

The values ​​in the first two columns can be transferred to any calling convention, for example, to query parameters. The result in body must match the third column. If the names and values ​​of the query are the names and values ​​of the columns, this reduces the differences between the test and the implementation.

For reuse, steps can be written for an arbitrary service that does calculations or determines the outcome of a business rule. In the above example, using the “?” Symbol, as in the “Discount Amount” above, helps the analyzer distinguish between input and output.

Tests should also include failures, for example.

Customer CategoryOrder amount Discount Amount? Result
Good 100.00 USD 
1.00 USD 
Ok
Not so good  100.00 USD 2.00 USD  Parameter value invalid
Excellent  100.00 ZZZ  2.00 USD  Parameter value invalid

Conclusion


Microservices depend on other services and systems, which requires a clear specification of the interfaces and their accurate testing. This can be achieved by describing the behavior and interfaces defined by tests. Using BDD, the functionality of services is described by executable tests that focus on the semantics of operations, rather than syntax . Automation of such tests usually requires setting up stubs of other services, the behavior of which is described by their individual BDD tests.

Interface-oriented design - IOD, includes additional obligations of the service: restriction on the use of resources, bandwidth and error reporting. Together, BDD and IOD help describe the behavior of the service so that customers can easily understand and rely on it.

  • BDD for microservices focuses on the cooperation of the triad - the service developer, client developer and tester.
  • Create clearly defined conventions for microservice interfaces using the IOD.
  • Microservices typically require test plugs to speed up testing.
  • Tests must be independent.
  • Test negative scenarios in tests.
On May 27-28, during the RIT ++ festival, at the QualityConf conference , Artyom Malyshev will talk about the importance of clearly expressing the domain model in the code and show how to do this using examples.

Come talk about developing quality products and share your ideas!

Also popular now: