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 and addressed quality concerns, data processing vs. data storage character, read and/or write access needs, and required access control and data protection. As a consequence, the endpoint serves in 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 might influence each other. For instance, if one of them is long-running or causes a 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).

Initial Position Sketch

The as is-design for this interface refactoring is (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:

  • Endpoint (for instance, an HTTP resource identified with a URI)
  • One of its operations (for instance, an HTTP verb/method supported by the resource)

Smells / Drivers

Role and/or responsibility diffusion
The endpoint is both an Information Holder and a Processing Resource (or works with different types of data, for instance both master/reference data and operational data). Its operations have rather diverse responsibilities. Hence, it is hard to explain its functionality coherently.
Low cohesion
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, e.g., the Spring REST controllers.
  4. Create an API Description for the new endpoint, featuring the extracted operation.
  5. Test whether the source endpoint still meets its now reduced API Description, also known as service contract (both in terms of functional and non-functional characteristics). Create an API Description for the new endpoint that only exposes the extracted operation.
  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).

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 (notation: Context Mapper DSL (CML)):

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); 
    }
}

This single publication management endpoint can then be split into two ones, leading to this API 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

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 actually improve as desired (loose coupling of original and new endpoint).
  • Maintainability does not suffer because of design erosion, duplication of published language, and so on.

This refactoring is reverted by Merge Endpoints.

Segregate Commands from Queries describes endpoint extraction for a particular reason. Move Operation is related as well.

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 approach may benefit from this refactoring (both applied to the strangled legacy system and to the strangling new implementation); a service-based system comprising multiple endpoints is generally easier to replace incrementally eventually than a more monolithic one.

References

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