🎯 Key Takeaways for quick navigation: 01:07 🧐 Custom HTTP headers don't need an "X-" prefix, as it was deprecated over a decade ago. Don't follow outdated practices just because they seem like industry standards. 03:12 🚀 You don't have to treat your HTTP API as strictly CRUD over resources. Consider being explicit about the actions users need to perform, even if they don't fit CRUD operations. 05:20 🔄 Rethink API versioning; it might be unnecessary. Avoid putting the burden of handling breaking changes on consumers and use hypermedia to evolve your API without versioning the entire thing. 07:54 🤖 Don't blindly follow industry trends or standards. Evaluate what makes sense for your specific context and system rather than just adopting practices because others are doing it. 08:38 📚 Join a community to discuss software architecture and design if you're interested in these topics, and always seek to learn and improve your understanding of best practices.
I'm enjoying your channel. I'm honestly not totally on board with some of your opinions, but you're clearly an expert, and your videos are extremely bingeable. I'm learning things! Thanks for making these.
How does the hypermedia approach (passing the action URLs with the response) work well with eventual consistency? If my process takes just a little longer than the first request to my next action, then the client will receive a 404 because the new resource does not yet exists. So what are strategies for this scenario? How do you handle that?
The first and second points make totally sense to me! But I am not sure about the third one... I mean, your advice is like: Increase the complexity of the APIs, make them more unfriendly, to avoid having breaking changes every 2-4 years, because usually this is the time that the versioning is changing in the real world! On the other hand, having the versioning up to the URL keeps both sides safe about the API. Idk man, I would like to hear your thoughts on this
I've never really understood the "hypermedia adds complexity" take. From a consumers perspective, you're giving it instructions at runtime so it actually has to understand less at design time (when you're building the client). Everyone has different experiences, so our views/opinions are based on that. I have an integration with a 3rd party that has multiple versions and the amount of work we have to move those integrations isn't trivial. I have other integrations that have never versioned and always made everything backwards compatible. The difference in both of those situations is one is putting burden on the consumer and the other isn't.
One misconception I've often seen is that http 404 is treated as "the resource does not exist" by the client. In reality the resource not existing is just one out of multiple possible things that can cause a 404. Effectively (based on the status code alone) the client must only treat it as, "the resource is unavailable, it may or may not exist and this may or may not be only temporary."
Except for the HEADER the X factor part, I don't think industry has any misconception of using when to use what HTTP verb, on API versioning again versioning in URI or via Header again is the good practice, robust and scalable way. (my opinion)
IMHO you should always version your APIs, but you should also try to avoid making breaking changes. If you have v1 in your route and you never need it, that's fine. If you don't version and then you do need it then it's a pain. After an API has existed for a long time you end up with a lot of deprecated code that you can't remove, which is just noise to any consumer, and you end up with a lot of tech debt where you wish you did something differently years ago and now you're stuck with it. Links in the response can help with this but they don't solve the whole problem.
Regarding custom headers, just because using "x" is no longer required doesn't mean it shouldn't be used. I like knowing instantly that it is custom when the x is there.
I share your hatred of REST dogma. I have yet to see a job posting with the experience requirement "RESTful API development" that means anything other than "JSON API over HTTP that's generally CRUD" 😂 Thanks for making these.
The funny thing is REST is *basically* just a naming convention over 1:1 resource mapping. The distinction is that "resource" doesn't always have to mean "a single table in the database" but when a company legitimately tries to follow REST, that's what ends up happening. But yeah, the vast majority of the time, companies don't even implement *that,* they just have a complete hodgepodge of route naming and it's not RESTful in the least. My current job's "REST"ful API has an endpoint named "GetJsonSchemaOverContext2" lol, where a RESTful convention would absolutely be something like "[Get] /v2/schema/json" (note: json is a real type in this context and not just the content type of the response). And I don't think REST has problems with extension actions, so something like "[Put] /order/cancel" is completely fine under REST. The important part is that the start of the route, "/order/" cleanly identifies the resource you're working with, and that the HttpVerb cleanly identifies what kind of side effects to expect from the action. The biggest stumbling block I've seen so far is if you have a search with complex parameters that are more suited to a JSON Body than query parameters, and while you *can* send a request body with a [Get] it's certainly its own break of convention to do so, and can cause issues with certain frameworks, so people [Post] those searches instead.
Good content, but I disagree on the last point. Absolutely version your API. Even if you’re using hypermedia, requirements change and the hypermedia structure you picked may change. But don’t force your consumers to change to a new version. This is very straightforward if you’re using absolutely no logic in your controllers and religiously using DTOs for in the API layer: your domain entities can cleanly map to and from previous versions of the DTOs and the application itself doesn’t need to know or care about API versions.
Creating different routes to add new functionality, depending how you look at it, is versioning. However, you're not forcing consumers to upgrade to an entire new API but rather specific new routes that provide new functionality. Deprecating old routes is still a thing. The point is not mass versioning your entire API for one breaking change to a specific route.
@@CodeOpinion Everything you've mentioned around versioning is really just expanding capabilities and functionality. But if we go down the no versioning route, how would you handle real breaking changes? i.e. maybe removing properties because they are replaced by a different set?
@@CodeOpiniontwo versions means x2 support, x2 vulnerabilities. So i agree, create a new method and keep both if not simply discontinue the old method
As with everything, it depends A vast majority of the APIs that we build are consumed by the same team and the “same” application. E.g line of business apps. In these cases, it makes little sense to support versioning out the box, because in most cases, breaking changes are acceptable
@@kabal911I think these cases are quite rare, though. Unless you have maintenance windows where everything is down, there’s a period where there can be a version mismatch between API provider and consumer. Unless you’re doing an atomic deploy of your consumer and your service, but then perhaps REST isn’t the best choice? Either way, even if planned downtimes are acceptable I’d rather deal with versioning than having to care about planning downtimes.
@6:00 I always knew it that API versioning is a bad idea (or at least not as could as it sounds like!). I never done it either, always extends new routes/paths since will never run out of English word !
Great video as usual! Of course, I'm focusing my comment on my only slight point of contention which has to do with the "objects as resources" and CRUD operations chapter in your video. This doesn't take away from the entirety of the video which is amazing as per usual. But, acknowledging this, I don't see much value commenting on the things I agree with and I think it's more interesting and productive to discuss the point of contention. I don't disagree with it this point entirely either (which is don't get stuck trying to force your API into a resource-based approach), but I want to offer some nuance for whom it may concern. Indeed, it's not a good practice to have a 1:1 mapping between the resources you're exposing through your API and your domain model. However, I'm also not a fan of exposing operations as part of the route. This position is philosophical, on the one hand, based on the idea that HTTP is defined around resources and the uniform interface concept from REST is a very sound idea. However, there's another argument for not exposing operations in your routes and sticking with resources (with the understanding that these don't have to, and often shouldn't, map to your entities). Specifically, the API modelling exercise in trying to figure out what would be the resources you're planning to expose often provides insights into the domain model itself and many times leads to a much more intention-revealing API than could be achieved by having operations in routes. Having operations in routes is such a low-effort approach that it can become lazy and might lead to skipping a deliberate and purposeful modelling exercise because that's much harder. Concrete example: Assume I have a system which manages employee information in an organization. An employee has a bunch of information (id, firstName, lastName, etc.) but they also have a manager. Now, the manager is a domain concept but may not be a domain entity (depending on the specific domain and business rules it may be the case that there's no behavior associated to require a Manager aggregate route or entity). In such a case, in practical terms, the manager domain concept is simply implemented as a managerId property on the Employee entity. However, despite the technical implementation lacking a Manager entity, the manager remains a domain concept. So, if we want to expose an operation for updating an employee's manager through our API, we have two options: 1. Operation-based (or verb-based): PUT /employees/123/update-manager. In this case the new manager id is provided as an integer in the body of the request. Simple, trivial, low-effort API design with no modelling effort and no insights into the fact that despite no Manager entity, the manager concept is still an important domain concept. However, notice already the arbitrariness of me having chosen PUT as the HTTP method of choice where a case could just as easily be made that POST would have been the better choice. And this is part of the issue with the operation-based approach because you're essentially, realizing or not, building a new protocol on top of HTTP and merely using HTTP as a tunnel for your own protocol. At this point, you might as well use POST for all mutating operations because the HTTP verb doesn't matter since the operation is an imperative verb in the route. 2. Resource-based (or noun-based): PUT /employees/123/manager/234. In this case the new manager id is provided as an integer in the route itself. So what's the advantage over the operation-based approach and why does this lead to more domain insights and and a more intention-revealing API? Because, for one, this approach makes the manager concept a first-class citizen of our ubiquitous language up to and including our API definition. And, secondly, because we can treat the manager as a first-class concept despite not relying on a Manager entity. For instance, the following API route become self-explanatory and perfectly intention revealing: GET /employees/123/manager An alternative approach would have been to expose these routes instead (again, perfectly domain-aligned and intention revealing despite the technical implementation lacking a Manager entity): PUT /manager/123/employees/456 GET /manager/123/employees This is such a trivial API that the power of this approach might not be clearly visible, but consider a more complex case where we're dealing with entity statuses such as a project planning system where projects can be approved. If we treat the concept of a project being approved as a domain concept, despite not having a backing entity, instead of an operation-based approach we can treat approvals as a resource. Something like this: PUT /approvals/123 -> moves the project 123 into the "approved projects collection" which is a virtual resource, not backed by an entity, yet expressing a domain concept and intent DELETE /approvals/123 -> removes the project 123 from the "approved projects collection" thus the project is no longer approved. The following more direct and alternative would also work PUT /projects/123/approved. However, we still can't completely get rid of the virtual "approved projects collection" resource for the following use cases GET /approvals -> which returns the list of approved projects. Of course this supports query parameters to filter by date, take, skip, etc. GET /pending -> this manifests another domain concept referring to the fact that in our domain a project can be pending approval. This route would very clearly manifest the intent of retrieving all such projects. In short, we're using HTTP verbs to retrieve and mutate resources in a pretty CRUD fashion, however, these resources don't map to entities necessarily and therefore we're not employing a CRUD interaction with our domain. For example, assuming there's a project portfolio domain concept in our domain, backed by a Portfolio entity, and there are behaviors and business rules surrounding approving a project the operation PUT/approvals/123 (or PUT /projects/123/approved ) despite being CRUD on the surface and driven by HTTP verbs, behind the scenes would do something like: var portfolio = portfolioRepo.GetPortfolio(); portfolio.ApproveProject(123); Now that I think about this in real-time, perhaps we have multiple portfolios, so the following routes would be even better: POST /portfolios GET /portfolios GET /portfolios/123 PUT /portfolios/123 DELETE /portfolios/123 PUT /portfolios/123/approved-projects/456 GET /portfolios/123/approved-projects GET /portfolios/123/approved-projects/456 -> This is interesting because it provides insights that the Approval is a domain entity itself with an approved date, approver and approved reason PUT /portfolios/123/approved-projects/456 -> So then we can modify things about the approval itself DELETE /portfolios/123/approved-projects/456 GET /portfolios/123/pending-projects I guess it's clear where this is going. Had I started with operations such as POST /portfolios/123/approve-project then I might have missed that, hey, an approval is an entity with an id, approvalDate, approverId and approvedReason. Then I would have an inchoesive API that combines operation-based routes (to approve a project) with resource-based routes (to manage the approvals). Note that I'm not suggesting this is a good design for this specific domain, instead I'm using this half-thought imaginary system as an example to prove a point. In closing, I'm not saying that it is always necessary or superior to go the resource-based approach over the more imperative operation-based approach but I'm suggesting that we shouldn't forego with the resource-based approach too soon either because, regardless of the path we end up choosing, spending some effort trying to design within the constraints of what the resource-based approach allows can help improve our domain insights, at least, and even the improve the intention-revealing of our API, at best. I hope this makes some sense.
I got tired of REST-based services a long time ago. I design my API's more like RPC's calls. Everything uses a POST. Example names look like: "cancel-order", "allocate-inventory", and "get-pick-list".
Interesting idea. Might just be me but in your example where you’re adding to the links array, I’m not sure how this would ever be a breaking change at all? In my experience, the breaking changes are in the request/ response payloads. IMO depending on links/routes from the server rather than being strictly defined in the API is only the tip of the iceberg when it comes to API changes. Said another way, adding a new “search” route to the existing versioned API is what I would do, no need to mess with links. But I understand that you’re just trying to show an example
I don't see versioning an API as a burden on the consumer/customer. I see it as guarantee, that i won't break their app/flow. All it takes is one incompatible feature when you realize you need api versioning. IMHO it's almost guaranteed it will come sooner than later. As soon as your api is public, you can not to afford not to use api versioning.
This is why I use JsonRPC approach instead of REST. Action-based APIs are much more natural to use and I always ask myself, when I hear about REST is "why?". Really, why REST? JsonRPC can also contain hypermedia and is so much simpler to use. Why struggle to map HTTP methods to API actions? Regarding versioning, I would version actions in JsonRPC, not the whole API. Unless the whole or big part of the API changes.
99% of the time, best practices makes sense in any context. Infact, you hardly go wrong following best practices. The issue is most devs don't follow best practices, think they are smarter, create their custom implementation that doesn't consider several things and introduce lots of security holes, bugs and create unmaintainable mess too.
Best practices require context, without it, your applying something blindly. Best practices should be called "maybe a good idea in this context, here are the trade-offs".
@@awmy3109 Example, with EDA/messaging, let's say you need consistency between state changes and publishing events a broker. "Best Practice" is using the outbox pattern. There are trade-offs with everything. The trade-off with that is you're going to increase load on your primary database. The alternative is using a fallback when you fail to publish, to either some durable storage elsewhere and having a separate mechanism as a retry to pull from that durable storage to attempt to publish. You didn't add more load to your primary DB, but you lost strong consistency. Doing something "right" depends on your context. In my example, volume, the type of events and what they are used for all play a role in deciding what solution you require, not just to follow the "best practice".
@@CodeOpinion If you really needed consistency, the extra load on your dB is a good tradeoff in my opinion. Best practices come with tradeoffs but they are best practices because their benefits are more than that of other options. Another example is implementing SSO. Do you ditch the OpenId industry standard because it is hard to implement, requires extra network calls or roll your own that is simple but introduces security flaws?
🎯 Key Takeaways for quick navigation:
01:07 🧐 Custom HTTP headers don't need an "X-" prefix, as it was deprecated over a decade ago. Don't follow outdated practices just because they seem like industry standards.
03:12 🚀 You don't have to treat your HTTP API as strictly CRUD over resources. Consider being explicit about the actions users need to perform, even if they don't fit CRUD operations.
05:20 🔄 Rethink API versioning; it might be unnecessary. Avoid putting the burden of handling breaking changes on consumers and use hypermedia to evolve your API without versioning the entire thing.
07:54 🤖 Don't blindly follow industry trends or standards. Evaluate what makes sense for your specific context and system rather than just adopting practices because others are doing it.
08:38 📚 Join a community to discuss software architecture and design if you're interested in these topics, and always seek to learn and improve your understanding of best practices.
avoided the REST dilemma, well played.
How does the consumer of the API handle changes?
If you make a breaking change via hypermedia. The consumer still needs to react to that change.
I'm enjoying your channel. I'm honestly not totally on board with some of your opinions, but you're clearly an expert, and your videos are extremely bingeable. I'm learning things! Thanks for making these.
"Does it even make sense anymore?" ... that one question alone freed my mind.
How does the hypermedia approach (passing the action URLs with the response) work well with eventual consistency? If my process takes just a little longer than the first request to my next action, then the client will receive a 404 because the new resource does not yet exists. So what are strategies for this scenario? How do you handle that?
The first and second points make totally sense to me! But I am not sure about the third one...
I mean, your advice is like: Increase the complexity of the APIs, make them more unfriendly, to avoid having breaking changes every 2-4 years, because usually this is the time that the versioning is changing in the real world!
On the other hand, having the versioning up to the URL keeps both sides safe about the API.
Idk man, I would like to hear your thoughts on this
I've never really understood the "hypermedia adds complexity" take. From a consumers perspective, you're giving it instructions at runtime so it actually has to understand less at design time (when you're building the client). Everyone has different experiences, so our views/opinions are based on that. I have an integration with a 3rd party that has multiple versions and the amount of work we have to move those integrations isn't trivial. I have other integrations that have never versioned and always made everything backwards compatible. The difference in both of those situations is one is putting burden on the consumer and the other isn't.
One misconception I've often seen is that http 404 is treated as "the resource does not exist" by the client.
In reality the resource not existing is just one out of multiple possible things that can cause a 404.
Effectively (based on the status code alone) the client must only treat it as, "the resource is unavailable, it may or may not exist and this may or may not be only temporary."
Except for the HEADER the X factor part, I don't think industry has any misconception of using when to use what HTTP verb, on API versioning again versioning in URI or via Header again is the good practice, robust and scalable way. (my opinion)
IMHO you should always version your APIs, but you should also try to avoid making breaking changes.
If you have v1 in your route and you never need it, that's fine. If you don't version and then you do need it then it's a pain. After an API has existed for a long time you end up with a lot of deprecated code that you can't remove, which is just noise to any consumer, and you end up with a lot of tech debt where you wish you did something differently years ago and now you're stuck with it.
Links in the response can help with this but they don't solve the whole problem.
Regarding custom headers, just because using "x" is no longer required doesn't mean it shouldn't be used. I like knowing instantly that it is custom when the x is there.
Sure, you absolutely can do it. The point being is that people think you have to.
Rest, 15 years I still need to see one. Many levels 1 or 2, but I have never seen a rest service. JSON api, nothing wring with it
I share your hatred of REST dogma. I have yet to see a job posting with the experience requirement "RESTful API development" that means anything other than "JSON API over HTTP that's generally CRUD" 😂
Thanks for making these.
The funny thing is REST is *basically* just a naming convention over 1:1 resource mapping. The distinction is that "resource" doesn't always have to mean "a single table in the database" but when a company legitimately tries to follow REST, that's what ends up happening. But yeah, the vast majority of the time, companies don't even implement *that,* they just have a complete hodgepodge of route naming and it's not RESTful in the least. My current job's "REST"ful API has an endpoint named "GetJsonSchemaOverContext2" lol, where a RESTful convention would absolutely be something like "[Get] /v2/schema/json" (note: json is a real type in this context and not just the content type of the response). And I don't think REST has problems with extension actions, so something like "[Put] /order/cancel" is completely fine under REST. The important part is that the start of the route, "/order/" cleanly identifies the resource you're working with, and that the HttpVerb cleanly identifies what kind of side effects to expect from the action.
The biggest stumbling block I've seen so far is if you have a search with complex parameters that are more suited to a JSON Body than query parameters, and while you *can* send a request body with a [Get] it's certainly its own break of convention to do so, and can cause issues with certain frameworks, so people [Post] those searches instead.
Good content, but I disagree on the last point. Absolutely version your API. Even if you’re using hypermedia, requirements change and the hypermedia structure you picked may change. But don’t force your consumers to change to a new version. This is very straightforward if you’re using absolutely no logic in your controllers and religiously using DTOs for in the API layer: your domain entities can cleanly map to and from previous versions of the DTOs and the application itself doesn’t need to know or care about API versions.
Creating different routes to add new functionality, depending how you look at it, is versioning. However, you're not forcing consumers to upgrade to an entire new API but rather specific new routes that provide new functionality. Deprecating old routes is still a thing. The point is not mass versioning your entire API for one breaking change to a specific route.
@@CodeOpinion Everything you've mentioned around versioning is really just expanding capabilities and functionality. But if we go down the no versioning route, how would you handle real breaking changes? i.e. maybe removing properties because they are replaced by a different set?
@@CodeOpiniontwo versions means x2 support, x2 vulnerabilities. So i agree, create a new method and keep both if not simply discontinue the old method
As with everything, it depends
A vast majority of the APIs that we build are consumed by the same team and the “same” application. E.g line of business apps.
In these cases, it makes little sense to support versioning out the box, because in most cases, breaking changes are acceptable
@@kabal911I think these cases are quite rare, though. Unless you have maintenance windows where everything is down, there’s a period where there can be a version mismatch between API provider and consumer. Unless you’re doing an atomic deploy of your consumer and your service, but then perhaps REST isn’t the best choice?
Either way, even if planned downtimes are acceptable I’d rather deal with versioning than having to care about planning downtimes.
Awesome! I always felt kind of guilty of not treat my API's always as CRUD 😅
@6:00 I always knew it that API versioning is a bad idea (or at least not as could as it sounds like!). I never done it either, always extends new routes/paths since will never run out of English word !
I wouldn't say it's a bad idea, I just wouldn't jump to it immediately by doing something like defining it in the URI/header/etc.
could somebody re-explain me what he said about why we don't need api versioning, my English listening is bad, thank you so much
You can enable subtitles using the "cc" overlay button in the player and read the text
Great video as usual! Of course, I'm focusing my comment on my only slight point of contention which has to do with the "objects as resources" and CRUD operations chapter in your video. This doesn't take away from the entirety of the video which is amazing as per usual. But, acknowledging this, I don't see much value commenting on the things I agree with and I think it's more interesting and productive to discuss the point of contention. I don't disagree with it this point entirely either (which is don't get stuck trying to force your API into a resource-based approach), but I want to offer some nuance for whom it may concern.
Indeed, it's not a good practice to have a 1:1 mapping between the resources you're exposing through your API and your domain model. However, I'm also not a fan of exposing operations as part of the route. This position is philosophical, on the one hand, based on the idea that HTTP is defined around resources and the uniform interface concept from REST is a very sound idea. However, there's another argument for not exposing operations in your routes and sticking with resources (with the understanding that these don't have to, and often shouldn't, map to your entities). Specifically, the API modelling exercise in trying to figure out what would be the resources you're planning to expose often provides insights into the domain model itself and many times leads to a much more intention-revealing API than could be achieved by having operations in routes. Having operations in routes is such a low-effort approach that it can become lazy and might lead to skipping a deliberate and purposeful modelling exercise because that's much harder.
Concrete example: Assume I have a system which manages employee information in an organization. An employee has a bunch of information (id, firstName, lastName, etc.) but they also have a manager. Now, the manager is a domain concept but may not be a domain entity (depending on the specific domain and business rules it may be the case that there's no behavior associated to require a Manager aggregate route or entity). In such a case, in practical terms, the manager domain concept is simply implemented as a managerId property on the Employee entity. However, despite the technical implementation lacking a Manager entity, the manager remains a domain concept. So, if we want to expose an operation for updating an employee's manager through our API, we have two options:
1. Operation-based (or verb-based): PUT /employees/123/update-manager. In this case the new manager id is provided as an integer in the body of the request. Simple, trivial, low-effort API design with no modelling effort and no insights into the fact that despite no Manager entity, the manager concept is still an important domain concept. However, notice already the arbitrariness of me having chosen PUT as the HTTP method of choice where a case could just as easily be made that POST would have been the better choice. And this is part of the issue with the operation-based approach because you're essentially, realizing or not, building a new protocol on top of HTTP and merely using HTTP as a tunnel for your own protocol. At this point, you might as well use POST for all mutating operations because the HTTP verb doesn't matter since the operation is an imperative verb in the route.
2. Resource-based (or noun-based): PUT /employees/123/manager/234. In this case the new manager id is provided as an integer in the route itself. So what's the advantage over the operation-based approach and why does this lead to more domain insights and and a more intention-revealing API? Because, for one, this approach makes the manager concept a first-class citizen of our ubiquitous language up to and including our API definition. And, secondly, because we can treat the manager as a first-class concept despite not relying on a Manager entity. For instance, the following API route become self-explanatory and perfectly intention revealing:
GET /employees/123/manager
An alternative approach would have been to expose these routes instead (again, perfectly domain-aligned and intention revealing despite the technical implementation lacking a Manager entity):
PUT /manager/123/employees/456
GET /manager/123/employees
This is such a trivial API that the power of this approach might not be clearly visible, but consider a more complex case where we're dealing with entity statuses such as a project planning system where projects can be approved. If we treat the concept of a project being approved as a domain concept, despite not having a backing entity, instead of an operation-based approach we can treat approvals as a resource. Something like this:
PUT /approvals/123 -> moves the project 123 into the "approved projects collection" which is a virtual resource, not backed by an entity, yet expressing a domain concept and intent
DELETE /approvals/123 -> removes the project 123 from the "approved projects collection" thus the project is no longer approved.
The following more direct and alternative would also work PUT /projects/123/approved. However, we still can't completely get rid of the virtual "approved projects collection" resource for the following use cases
GET /approvals -> which returns the list of approved projects. Of course this supports query parameters to filter by date, take, skip, etc.
GET /pending -> this manifests another domain concept referring to the fact that in our domain a project can be pending approval. This route would very clearly manifest the intent of retrieving all such projects.
In short, we're using HTTP verbs to retrieve and mutate resources in a pretty CRUD fashion, however, these resources don't map to entities necessarily and therefore we're not employing a CRUD interaction with our domain. For example, assuming there's a project portfolio domain concept in our domain, backed by a Portfolio entity, and there are behaviors and business rules surrounding approving a project the operation PUT/approvals/123 (or PUT /projects/123/approved ) despite being CRUD on the surface and driven by HTTP verbs, behind the scenes would do something like:
var portfolio = portfolioRepo.GetPortfolio();
portfolio.ApproveProject(123);
Now that I think about this in real-time, perhaps we have multiple portfolios, so the following routes would be even better:
POST /portfolios
GET /portfolios
GET /portfolios/123
PUT /portfolios/123
DELETE /portfolios/123
PUT /portfolios/123/approved-projects/456
GET /portfolios/123/approved-projects
GET /portfolios/123/approved-projects/456 -> This is interesting because it provides insights that the Approval is a domain entity itself with an approved date, approver and approved reason
PUT /portfolios/123/approved-projects/456 -> So then we can modify things about the approval itself
DELETE /portfolios/123/approved-projects/456
GET /portfolios/123/pending-projects
I guess it's clear where this is going. Had I started with operations such as POST /portfolios/123/approve-project then I might have missed that, hey, an approval is an entity with an id, approvalDate, approverId and approvedReason. Then I would have an inchoesive API that combines operation-based routes (to approve a project) with resource-based routes (to manage the approvals).
Note that I'm not suggesting this is a good design for this specific domain, instead I'm using this half-thought imaginary system as an example to prove a point.
In closing, I'm not saying that it is always necessary or superior to go the resource-based approach over the more imperative operation-based approach but I'm suggesting that we shouldn't forego with the resource-based approach too soon either because, regardless of the path we end up choosing, spending some effort trying to design within the constraints of what the resource-based approach allows can help improve our domain insights, at least, and even the improve the intention-revealing of our API, at best. I hope this makes some sense.
I got tired of REST-based services a long time ago. I design my API's more like RPC's calls. Everything uses a POST. Example names look like: "cancel-order", "allocate-inventory", and "get-pick-list".
Interesting idea. Might just be me but in your example where you’re adding to the links array, I’m not sure how this would ever be a breaking change at all? In my experience, the breaking changes are in the request/ response payloads. IMO depending on links/routes from the server rather than being strictly defined in the API is only the tip of the iceberg when it comes to API changes.
Said another way, adding a new “search” route to the existing versioned API is what I would do, no need to mess with links. But I understand that you’re just trying to show an example
It seems that X- just convenient way to distinguish standart and custom headers by quick look.
Ya it can be for sure. But people assume that's the "correct way", when it's not.
I don't see versioning an API as a burden on the consumer/customer. I see it as guarantee, that i won't break their app/flow. All it takes is one incompatible feature when you realize you need api versioning. IMHO it's almost guaranteed it will come sooner than later. As soon as your api is public, you can not to afford not to use api versioning.
You always have at least one hot take 😂
I saw a few tutorials where they connect APIs to CRUD and it made me confused, so I procrastinated on learning API. So much time was wasted 😢
This is why I use JsonRPC approach instead of REST. Action-based APIs are much more natural to use and I always ask myself, when I hear about REST is "why?". Really, why REST? JsonRPC can also contain hypermedia and is so much simpler to use. Why struggle to map HTTP methods to API actions? Regarding versioning, I would version actions in JsonRPC, not the whole API. Unless the whole or big part of the API changes.
Very nice 👌
Good stuff
This channel really likes to go for controversial topics to get engagements
wtf is that shirt. No workout today?
Ha! Glad you noticed
99% of the time, best practices makes sense in any context. Infact, you hardly go wrong following best practices.
The issue is most devs don't follow best practices, think they are smarter, create their custom implementation that doesn't consider several things and introduce lots of security holes, bugs and create unmaintainable mess too.
Best practices require context, without it, your applying something blindly. Best practices should be called "maybe a good idea in this context, here are the trade-offs".
@@CodeOpinion Well, am yet to find that "context" where doing something right isn't the best option.
@@awmy3109 Example, with EDA/messaging, let's say you need consistency between state changes and publishing events a broker. "Best Practice" is using the outbox pattern. There are trade-offs with everything. The trade-off with that is you're going to increase load on your primary database. The alternative is using a fallback when you fail to publish, to either some durable storage elsewhere and having a separate mechanism as a retry to pull from that durable storage to attempt to publish. You didn't add more load to your primary DB, but you lost strong consistency. Doing something "right" depends on your context. In my example, volume, the type of events and what they are used for all play a role in deciding what solution you require, not just to follow the "best practice".
@@CodeOpinion If you really needed consistency, the extra load on your dB is a good tradeoff in my opinion. Best practices come with tradeoffs but they are best practices because their benefits are more than that of other options.
Another example is implementing SSO. Do you ditch the OpenId industry standard because it is hard to implement, requires extra network calls or roll your own that is simple but introduces security flaws?
You keep conflating rolling your own vs some pre-defined solution. I'm not suggesting that, and I have a video specifically about not doing it.