RESTful archiving of entities in WebAPI
Asked Answered
I

4

22

I've implemented CRUD functionality pretty restfully in my WebAPI project. I'm now trying to implement Archiving of objects (not quite deleting) - if only there were an ARCHIVE HTTP method.

I see two options:

1) Have isArchived as a property of every archive-able entity, which must be included in PUT and POST requests even if archiving isn't relevant to the request. Archiving an entity would be a matter of calling PUT /api/object/id with isArchived set to true. Seems bulky on the wire but restful.

2) Have an RPC-ish url like PUT /api/object/id/archive that doesn't require a body. Seems the most efficient but not restful.

What's everyone doing in the "archive my stuff via an api call" space?

Incorporator answered 24/4, 2013 at 20:49 Comment(0)
L
5

If you always archive a particular resource and never delete it, I would repurpose DELETE to actually archive. If you really need to differentiate between delete and archive, I would either do

GET /foo/33

200 OK
<foo id="33">blah</foo>


POST /archive
<foo id="33">blah</foo>

201 Created
Location: http://example.org/archive/foo/33

or just

POST /archive?target=http://example.org/foo/33

201 Created
Location: http://example.org/archive/foo/33
Land answered 25/4, 2013 at 11:30 Comment(0)
H
17

This is an excellent question, but I suspect it may eventually be marked as opinionated because I don't see a correct answer... as the OP also stated.

I would recommend treating the archive as a separate object store (or, even, different object types), if that makes sense for your system. Object design should not depend upon how the DB persists your data.

Thus, this is the most RESTful design I can come up with right now (assuming archiving and updating are always separate actions -- which they should be):

Typical (everybody knows this):

GET     /api/object     get all current objects
POST    /api/object     new current object
PUT     /api/object/id  update current object
DELETE  /api/object/id  delete current object
GET     /api/object/id  get current object

The weirdness:

POST    /api/object/id/archive  move object to archive (makes some REST sense)
POST    /api/object/id          move object from archive (muddy)

The archive:

GET     /api/object/archive     get all archive objects
PUT     /api/object/id/archive  update archive object (if possible)
DELETE  /api/object/id/archive  delete archive object (tempting for unarchive)
GET     /api/object/id/archive  get archive object

Or, maybe one of these mods for archive URLs:

GET     /api/object/archive/id  get archive object
GET     /api/objectarchive/id   get archive object

But......

The above feels pretty muddy (not very self-documenting) for moving objects in and out of the archive. It also leads to some REST API design pain where update/delete/get of an archived object probably don't need archive-specific functions. Thus, I ultimately settled on this:

GET     /api/object                     get all objects
GET     /api/object?archived=false      get all current objects
GET     /api/object?archived=true       get all archive objects

POST    /api/object         new current object, returns all current objects*
PUT     /api/object/id      update object (current or archived; cannot change archive state)
DELETE  /api/object/id      delete object (current or archived), returns objects of same archive state as deleted*
GET     /api/object/id      get object    (current or archived)*

PUT /api/object/id/archive body:{archived:true}     move object to archive, returns all current objects*
PUT /api/object/id/archive body:{archived:false}    move object from archive, returns all archive objects*

* Return could be expanded/overridden with a query string if design calls for it.

Admittedly, this is mostly a reversal from my earlier statement of treating the archive as a separate object store. Yet, that thought process is what ultimately led to this compromise in design. This feels good to me on most fronts.

I, personally, don't agree with using the query string for anything but... uh... queries. So, I don't. Payload for data changes -- no matter how small -- should go in the body (when it doesn't fit with a REST verb and URL, that is).

Hideaway answered 12/7, 2019 at 1:37 Comment(1)
Very well documented thought process. It is nice read how you actually came to the most optimal solution with good arguments.Segovia
L
5

If you always archive a particular resource and never delete it, I would repurpose DELETE to actually archive. If you really need to differentiate between delete and archive, I would either do

GET /foo/33

200 OK
<foo id="33">blah</foo>


POST /archive
<foo id="33">blah</foo>

201 Created
Location: http://example.org/archive/foo/33

or just

POST /archive?target=http://example.org/foo/33

201 Created
Location: http://example.org/archive/foo/33
Land answered 25/4, 2013 at 11:30 Comment(0)
H
1

I'd use the /api/object/id?archive=true approach.

But, as to whether you should use PUT or POST depends. If you use PUT, any subsequent calls to the same URL would not change anything about the resource. If you use POST, the implementer expects that any subsequent calls to that URL will indeed change the state. (Don't ask me how, I'm assuming that you will use the PUT verb on this one.)

This is due to the fact that PUT operations should be idempotent. See section 9.1.2 here: http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

Highkeyed answered 24/4, 2013 at 21:7 Comment(0)
S
0

I would probably use the /api/object/id endpoint with a query parameter, so it looks something like /api/object/id?isArchived=true. You can still use whatever HTTP verb you were using.

Soupy answered 24/4, 2013 at 20:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.