1

My first time trying to implement API versioning here, and I need some guidelines on how to do it...

I know there are many ways of doing API versioning. I'm already decided on doing it by uri (i.e, I'll have something like put mydomain.com/api/v1/resources/:resourceId) The question is not about 'referencing' the right version. Instead, what I'm trying to understand is how to organize and structure the artifacts...

My concrete problem is the following:

I have an endpoint put stores/:storeId which is already being used by a front-end application. Then I realised it would be safer and simpler for validation if I took storeId implicitly from req.user. So my endpoint would change to be just put stores

If it was already in production, the front-end application would have to change. That made think I should be prepared for situations like this, and started to look into how to version my API.

But if an endpoint is controlling some artifacts and its behaviour changes, it seems to me that in order to isolate the previous endpoint from future changes necessary to newer versions, all the artifacts used by the previous endpoint should also be versioned. Right?

Because if we don't and we have different API versions expecting different behaviours from a same factory or repository, that would be a problem.

If that assumption is correct, so how would I structure my artifacts? Should I replicate the whole structure for each API version? Or should I version only the elements that change from version to version?

I'll try to give a concrete exemple below:

My Back-end's file structure

I'm using DDD and have my domain layer artifacts structured like:

src/domain/entityType/modelName/artifacts

where:

  • domain could be the Core domain (with the main models and rules), Actors domain (modelling the actors and permissions) or a Support domain (entities for logging and other support roles)
  • entityType would be a service, factory or repository
  • modelName are the main business entities of the domain artifacts would be files for the Mongoose schema, rules specifications, adapters, and the source code for factories, repositories or services that use those artifacts, with their respective test files as well

Example

For example, I could have a service which would allocate tasks to an employee. That service would be structured in:

src/core/services/taskAllocation/

where I would have taskAllocation.service.js, taksAllocation.rules.js

And taskAllocation.service.js would reference taskAllocation.rules.js and also src/core/repositories/employee/employee.repo.js and src/core/repositories/tasks/tasks.repo.js

And my application layer is structured like:

src/routes/domainName/modelName

where I'll have modelName.router.js, modelName.controller.js, modelName.validations.js (with rules for input validations)

The router contains the use cases associated to the specific modelName. And the controller will access and coordinate those src/domain entities defined above.

For the case in the example, there would be a folder src/routes/employess/employees.router.js. That would be accessed through an endpoint post employees/:id/tasks, which would call employeesController.allocateTasksById(req.employee.id), which would call the respective service method in taskAllocation.service.js at the domain layer.

The replication problem

This is how I have done so far. Now suppose I will create versions for the API, like: src/routes/v1/domainName/modelName and src/routes/v2/domainName/modelName And the only difference in those APIs is now my original put stores/:storeId is deprecated and put stores should be used instead, which uses storeId from req.user.storeId. No artifact has changed. The change is restricted to the application layer.

But if I keep both controllers (from API v1 and v2) accessing the same domain layer artifacts, someday someone could change some artifact and invalidate v1. It seems to me that the artifacts should be replicated (that would be an "accidental" duplication, right?, not a true one...)

And I thought about 2 options: either I version and replicate the whole src/domainName structure, like src/v1/domainName, or I could version and replicate only the domain artifacts referenced by the changed endpoint, which would be:

src/core/repositories/employees/v2
src/core/repositories/tasks/v2
src/core/services/tasksAllocation/v2

Well, as I said, it's my first time api-versioning... In all the articles I've read about api versioning, I only saw the "naming" problem being talked about. I've never seen anything about managing and structuring the artifacts. So guide me here, please.

  • stores/:storeId is deprecated and put stores does "deprecated" means that you don't provide backward compatibility? Or it still must exist and provide with any sort of message or redirection to the new interface? – Laiv Apr 3 at 13:27
  • well, in my current case, I don't need to keep backwards compatibility, because the system is not yet in production... but in a real-life case, I would have to keep it until all users had updated their apps to the new version – João Otero Apr 3 at 13:56
1

Personally, I wouldn't store multiple API versions in one codebase. This leads to many trouble when there are differences in the business rules of different versions.

Instead, I create maintenance Git branches for each running API version. For example:

  • master: the current API version (e.g 1.4)
  • maintenance/1.3: the code of 1.3.x version
  • maintenance/1.2: the code of 1.2.x version

On the production environment, each maintenance branch is deployed to a dedicated server(s). A load balancer stands in front of them to route the traffic to the correct server(s) based on the version in the URI.

When new patches are released, I used cherry-pick to apply the fix to all branches that the patches apply to. Because my product API is also consumed by external clients, we have a communication channel to announce to the clients when an old version was deprecated.

The most important thing is to follow the semver and keep the backwards compatibility inside one maintenance branch.

  • I appreciate your answer. Makes sense to me. I will just wait to see if other opinions emerge prior to accept it. – João Otero Apr 3 at 13:58
1

well, in my current case, I don't need to keep backwards compatibility, because the system is not yet in production... but in a real-life case, I would have to keep it until all users had updated their apps to the new version

Versions come in many flavours1 and usually, the solution conveys with the company' business strategy for the app. Whether you "would" or not version, remains to be seen and decided. Your doubts come from the fact that you are trying to solve a problem that has not yet happened and that makes you see (or not) the so many "flavours" you can pick.

That said, don't version things if you don't need it. At the moment, you can replace|remove deprecated code, making the source code to be clean and concise. Use the SCM to retrieve old implementations and track changes, don't do it through the source code itself (comments or duplicated components).

Later, if you have to version, consider doing versions of components (classes, libraries, etc) instead of namespaces because backward compatibility is easier to treat with code (via OOP principles, patterns and another programming practices) than with namespaces or directories.2

If you find yourself maintaining N different versions of the source code and they have different life cycles, then implement the branch strategy that best meets your needs for maintainability, deployment and delivery. Or fork the code. Whatever route you pick.

As for versioning the web API. That's a whole different subject that worth its own question on S.E.

Note: There're alternatives to the well-known versioned URIs, what in my opinion is a huge mistake


1: source code versioning, app versioning, web API versioning, ... Each of which implementing different strategies
2: The later should tell about the application, about the business rather than versions and other details of the sort.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.