Extract Endpoint

Updated:

 Note: This is a preview release and subject to change. Feedback welcome! Contact Information and Background (PDF)

also known as: Split Endpoint

Context and Motivation

The drivers for this refactoring are very similar to those of Move Operation: One or more API endpoints, for instance, HTTP resources, have been developed, tested, and deployed. One of these endpoints contains multiple operations (for instance, HTTP PUTs, POSTs, and so on). These operations work with multiple domain concepts. Their functional and technical responsibilities differ in terms of stakeholder groups (users, developers, …) and addressed quality concerns, data processing versus data storage character, read and/or write access needs, and required access control and data protection. As a consequence, the endpoint serves multiple roles in the API architecture.

As the API provider, I want to focus the responsibilities of an endpoint on a single role so that a) API clients serving a particular stakeholder group understand the API design intuitively and b) the release roadmap and scaling of the endpoint can be optimized for each group of stakeholders and clients.

Stakeholder Concerns (including Quality Attributes and Design Forces)

#scalability and #reliability
When co-located in a single endpoint and deployed jointly, operations share the same execution context and resources and might therefore influence each other. For instance, if one of them is long-running or causes an API provider-internal error, its siblings might suffer from quality-of-service degradations too.
#single-responsibility-principle and #independent-deployability
Architectural principles can guide the selection of refactoring. Some are affected positively, some are affected negatively. The Purposeful, style-Oriented, Isolated, channel-Neutral, and T-shaped (POINT) principles for API design fall in this category; extracting an endpoint can improve P, O, and I (but might harm T when looking at a single endpoint and not an entire API).
#security
With multiple operations co-located within a single endpoint, it can be challenging to enforce fine-grained access control policies. Refactoring this endpoint into multiple specialized ones allows for more granular control over access permissions and authorization rules. The security requirements of the data handled by the endpoint may also differ, so separating operations can make it easier to apply data protection measures.

Initial Position Sketch

The as is-design for this interface refactoring looks as follows (notation: MDSL):

endpoint type SomeEndpoint
exposes 
  operation operation1 
    expecting payload "RequestMessage1" 
    delivering payload "ResponseMessage1"
    
   operation operation2 
    expecting payload "RequestMessage2" 
    delivering payload "ResponseMessage2"

The refactoring targets are an API endpoint (for instance, an HTTP resource identified with a URI) and one of its operations (for instance, an HTTP verb/method supported by the resource).

Design Smells

Role and/or responsibility diffusion
The endpoint is both an Information Holder Resource and a Processing Resource. An Information Holder exposes different types of data, for instance, both master data and operational data. The endpoint operations have rather diverse functional and technical responsibilities. Hence, it is hard to explain the endpoint purpose.
Responsibility mishmash
The operations in the endpoint deal with multiple, not necessarily related domain concepts. Consequently, the endpoint has more than one reason to change during its evolution. It serves multiple stakeholder groups and/or its implementation is developed and maintained by multiple teams.

Instructions (Steps)

Extract Endpoint is similar to Move Operation, but creates a new endpoint (rather than adding to an existing endpoint):

  1. Remove the operation from the API Description of the source endpoint.
  2. Check the general security policies and the client rights management (source, target). For example, authorization rules that use endpoint existence and names to determine whether a client application and end-user are permitted to perform an operation might have to be adjusted.
  3. Refactor at the code level. For instance, create an additional REST controller when working with Java and HTTP in Spring and move the implementation of the chosen operation.
  4. Create an API Description for the new endpoint that only exposes the extracted operation.
  5. Test whether the source endpoint still meets its now reduced API Description (both in terms of functional and non-functional characteristics).
  6. Evaluate whether the roles and responsibilities of the two endpoints are well-separated and that the refactoring resulted in endpoints with higher cohesion.
  7. Inform all API clients about the change (in which version will it be introduced?) and provide migration information (or support the transition on a technical level, for instance, with an HTTP redirect).

If necessary, repeat these steps with the remaining operations until the roles and responsibilities of the endpoint have been clarified and the smells removed.

Target Solution Sketch (Evolution Outline)

This simple and abstract MDSL sketch specifies the result of the refactoring at an abstract level:

endpoint type SomeEndpoint
exposes 
  operation operation1 
    expecting payload "RequestMessage1" 
    delivering payload "ResponseMessage1"

endpoint type ExtractedNewEndpoint    
exposes
operation operation2 
    expecting payload "RequestMessage2" 
    delivering payload "ResponseMessage2"

Note that this sketch does not show signs of bad smells in terms of semantics or qualities yet; the following example does.

Example

Sometimes it makes sense to separate commands from queries (see Segregate Commands from Queries). This is a particular case of endpoint extraction. Hence, the following example can be seen as an example of both Segregate Commands from Queries and Extract Endpoint. It starts from a Domain-Driven Design (DDD) featuring a single Aggregate [Evans 2003]:

Aggregate PublicationEndpoint {
    Service PublicationManagementFacade {
        // a state creation/state transition operation:
        @PaperId add(@PublicationEntryDTO newEntry);
        
        // retrieval operations:
        @PublicationArchive dumpPublicationArchive();
        Set<@PublicationEntryDTO>lookupPublicationsFromAuthor(String writer);
        String renderAsBibtex(@PaperId paperId);
        
        // computation operations (stateless):
        String convertToBibtex(@PublicationEntryDTO entry); 
    }
}

The notation in the above snippet is Context Mapper Domain-Specific Language (CML)). CML supports both tactic and strategic DDD [Zimmermann and Stocker 2021].

This single publication management Aggregate (and API endpoint) can be split into two ones, leading to this design:

Aggregate PublicationCommandsEndpoint {
    Service PublicationManagementCommandFacade {
        // a state creation/state transition operation:
        @PaperId add(@PublicationEntryDTO newEntry);
        
        // computation operations (stateless):
        String convertToBibtex(@PublicationEntryDTO entry); 
    }
}

Aggregate PublicationQueriesEndpoint {
    Service PublicationManagementQueryFacade {
        // retrieval operations:
        @PublicationArchive dumpPublicationArchive();
        Set<@PublicationEntryDTO>lookupPublicationsFromAuthor(String writer);
        String renderAsBibtex(@PaperId paperId);
    }
}

Note that this design violates principles such as single responsibility and high cohesion, low coupling because Bibtex-related operations appear in both endpoints. It might make sense to apply Move Operation on convertToBibtex next or add a third endpoint that exposes the two BibTeX-related operations.

Hints and Pitfalls to Avoid

When applying this refactoring, API designers have to make sure that:

  • Concurrent access to business logic and database from two presentation layers a.k.a. API endpoints do not cause issues such as lost updates, phantom reads, deadlocks, etc.
  • Performance and independent deployability improve as desired (loose coupling of the original and new endpoint). Extracting an endpoint to focus on a single role redistributes the existing responsibilities and logic across multiple endpoints. This redistribution could affect the performance of the API, especially if there are increased interdependencies or additional network calls are introduced. Proper load testing and performance analysis should be conducted to ensure that the refactored API can handle the expected workload and provide satisfactory response times.
  • Maintainability does not suffer because of design erosion, duplication of published language, and so on. The refactored endpoints may have dependencies on other services or resources within the system. It is essential to carefully manage and coordinate these dependencies to ensure that the refactored endpoints can operate independently and reliably.

This refactoring is reverted by Merge Endpoints. Segregate Commands from Queries describes endpoint extraction for a particular reason. Move Operation has a similar purpose and nature, but does not create a new endpoint.

In code refactoring, there is Extract Method [Fowler 2018].

A Strangler Fig Application is an approach to enhance an existing system with new features without replacing it immediately. Such an approach may benefit from this refactoring (both applied to the strangled legacy system and the strangling new implementation); a service-based system comprising multiple endpoints is generally easier to update incrementally (and replace eventually) than a more monolithic one. See this Refactoring Legacy Code with the Strangler Fig Pattern blog post for a more detailed, step-by-step explanation.

References

Evans, Eric. 2003. Domain-Driven Design: Tacking Complexity in the Heart of Software. Addison-Wesley.

Fowler, Martin. 2018. Refactoring: Improving the Design of Existing Code. 2nd ed. Addison-Wesley Signature Series (Fowler). Boston, MA: Addison-Wesley.

Zimmermann, Olaf, and Mirko Stocker. 2021. Design Practice Reference - Guides and Templates to Craft Quality Software in Style. LeanPub. https://leanpub.com/dpr.