DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Data Engineering
  3. Databases
  4. RESTful Service-Design: How to Overcome the CRUD-nature of the REST-style

RESTful Service-Design: How to Overcome the CRUD-nature of the REST-style

Trying to overcome the CRUD aspect of RESTfunless? Here's a guide to RESTful service-design, with a telecom example.

Stephan Bauer user avatar by
Stephan Bauer
·
Jan. 13, 16 · Tutorial
Like (7)
Save
Tweet
Share
11.61K Views

Join the DZone community and get the full member experience.

Join For Free

On first sight, the architectural style of REST via HTTP seems to constrain us to simple CRUD APIs: We operate on "resources" which we often map rather exactly to our internal domain- / entity-types and we also mainly use the HTTP-Verbs GET (=retrieve), POST = (=Create + Update partly), PUT (=Update/Replace), and DELETE. But how can this fit if we need to implement some kind of business logic like validation, that is not directly related to one of the CRUD-operations? I want to show you a real life example and explain how I solved the problem.

The Real-life Challenge: Detect the Porting-Status of a "PhoneNumberBlock"

The challenge I was facing was the following: Our telecom system has a PhoneNumberBlock-Entity, which describes the start and the end of a block of contiguous phone numbers, for example:

  • start = 8931144500
  • end = 8931144599

=> As you can see, this is a block consisting of 100 contiguous phone numbers.

When a customer requests a new telephone connection, he can specify one or more PhoneNumberBlocks, that need to be associated with the new connection. The blocks can have any arbitrary size. Among other things, the system has to check, if the requested block is currently ported to another carrier. It might well be, that the phone numbers of a requested block have differing porting status, because a PhoneNumberBlock has a limited lifetime. Let’s assume the above PhoneNumberBlock was once associated to a connection, but that connection has been terminated in the meantime. The block gets removed 3 months after termination and the numbers of the block might have been given away separately or in smaller blocks or even as part of a bigger block for other connections.

At some point, we need some logic that determines the current PortingStatus of the requested block with the following Enum-Values:

  • PORTED_AWAY_COMPLETELY
  • PORTED_AWAY_PARTLY
  • PORTED_AWAY_AS_PART_OF_BIGGER_BLOCK
  • COMPLETELY_AVAILABLE

The reason for this is, that the system must refuse the request if the PortingStatus is either PORTED_AWAY_PARTLY or PORTED_AWAY_AS_PART_OF_BIGGER_BLOCK.

Now the question is: How should this logic be properly designed with a RESTful API? What we have is a separate domain (a "porting-microservice") that encapsulates the access to our database which contains all the porting information. From this database we must derive the above PortingStatus of the requested block.

First Naive Solution Approach

As stated above, we have to think in terms of Resources and we only have the HTTP-Verbs, so the first idea was to offer a PortedPhoneNumberResource with a findAllPortedAwayPhoneNumbers() method.

GET http://myserver/rootcontext/resources/phonenumbers?status=portedAway

In this case, it would be the caller’s responsibility to search through the returned numbers to detect the correct overall state of the block. But this is a bad solution in terms of "separation of concerns." The porting-microservice should not only deliver the data, it should – as far as possible – also implement the required logic around that data.

Second (Still Naive) Solution Approach With Constrained Find-Method

GET http://myserver/rootcontext/resources/phonenumbers?status=portedAway&start=8931144500&end=8931144599

Even if we constrain the find-method from the first solution with the start and end phone number of the requested block as query-parameters (findAllPortedAwayPhoneNumbersOfBlock), we still need the logic for finding out the status on the client side.

Third Solution Approach: The Status as a Resource

From the point of view of the client, it would be most desirable, if the REST-call could simply return the PortingStatus of the block. But then the PortingStatus would need to be a resource of its own. This doesn’t seem to make sense from a logical point of view, because a Status simply isn’t a resource.

Fourth solution approach: The Status as a Sub-Resource

What about a Sub-Resource? This seems to be more logical, but if we ask for a Sub-Resource in REST, we learned, that we need the ID of the parent resource, e.g.

GET http://myserver/rootcontext/resources/phonenumberblocks/1234/status

The problem is, that we don’t have the ID of the parent resource. Moreover, it is likely, that the block doesn’t even exist and thus there is in fact no ID that represents this block.

Are we stuck now? Can we throw away the RESTful style for things like this? The answer is "No!"

Fifth and Last Solution Approach: The Status as a Sub-Resource of a Logical Resource

A requested block has a business key: the start- and the end phone number. Why don’t we concatenate the two numbers to a logical id including some separation character:

GET http://myserver/rootcontext/resources/phonenumberblocks/8931144500-8931144599/portingstatus

Maybe we could even optimize the logical id by just telling the starting number and the size of the block, but that’s syntactic sugar. Anyway, the Response-Body would simply contain the String-Representation of the actual PortingStatus.

This is in my opinion a good solution as it combines both the RESTful API design style and the "separation of concerns" principle.

Conclusions

  • REST-Resources do not necessarily have to exist 1:1 as a persistent entity in the underlying database. Instead, they can be a logical abstraction. In other words: a DTO exposed via REST does not necessarily need to look exactly like the corresponding domain object/the entity.
  • Sub-Resources offer an additional way to make logical abstractions from the original resource: Another example of such a logical Sub-Resource for implementing Non-CRUD-logic would be the validation of a given Resource instance. My approach for this is to query for the validation messages as a subresource:
    • POST http://myserver/rootcontext/resources/phonenumberblocks/8931144500-8931144599/validationmessages
    • The resource to validate is in the request body.
    • If validation is OK, we return 200 OK and an empty response body
    • If validation fails, we return 422 "Unprocessable Entity" and the response body contains the list of validation messages (see https://en.wikipedia.org/wiki/List_of_HTTP_status_codes for status code 422).
  • This kind of design enables us to keep the related business logic in the service itself without violating the RESTful style. The key is the combination between logical resources (also for transient resource instances) and a proper choice of subresources.
  • List resources like "phonenumberblocks" in the above example might need to support both variants of IDs: The logical ID for calling business logic on transient objects and the real ID. Anyway, if your system is used by "unstrusted"/external clients, you should not expose your technical primary key from the database as an ID but instead use some unique business key, for example: a "customerNumber", "orderNumber", etc.
REST Web Protocols Blocks Service design Database Business logic

Published at DZone with permission of Stephan Bauer, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Full Lifecycle API Management Is Dead
  • Application Architecture Design Principles
  • NoSQL vs SQL: What, Where, and How
  • Spring Boot, Quarkus, or Micronaut?

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: