Web Application Design: CRUD copy actions with side effects
Given a user visiting the view for Wat copy
When the user clicks on the button to submit the Wat copy form
Then the Wat copy is created
And something else happens
And the user redirected to Wat show view for the copied Wat
In this post I want to consider how to design around side effects that might optionally occur in or as part of an action. I’ll just examine the copy feature from the last post since it’s convenient to think about, but this probably won’t be the last time we examine optional side effects. But it should introduce the problem well enough.
Here’s the abstract flow view of the copy-as-view feature we’ve been discussing. I arranged it a little differently from the last post.
Here I’ve dotted and hollowed the edges that represent links between views, and left solid the lines that represent regular actions. The abstract flow diagram we’ve been using is a little too abstract for further illustrations, but the idea is that both the “new” and “copy” views share a “create” action that redirects to “show” and this shows where we’re at in this implementation.
With that in mind, now let’s consider the features describing “new” and “copy”:
Given a user visiting the view for Wat new
When the user clicks on the button to submit the Wat new form
Then the new Wat is created
And the user redirected to Wat show view for the new Wat
Given a user visiting the view for Wat copy
When the user clicks on the button to submit the Wat copy form
Then the Wat copy is created
And something else happens
And the user redirected to Wat show view for the Wat copy
So our challenge comes from how “something else happens” during the
Wat copy
action that does not happen for the regular create action when
the form on the Wat new
view is submitted and our current implementation
has them using the same “create” action to get something done.
About the “something else” feature. I want to avoid specifying too much about it, because we’re looking for hints about design principles and there will be other examples. Also, we’re more interested in uncovering questions rather than coming up with definitive answers. We might ask:
- How does the feature affect the representation of resources?
- For example, perhaps copied resources are represented with links back to the resource it was copied from.
- How does the feature affect user interaction with resources?
- For example, perhaps a notification with a link to the copied resource is sent to interested parties.
But the only thing we need to expect for the design is that at least one of those questions has some details similar to the example answers, and that it happens in some circumstances but not others.
In the last post we considered two options for implementation:
- parameterize the existing
new
view or - create a specialized
copy
view.
There I said that, if it were necessary to represent the original resource we were copying from, it seemed better to create a member route and specialized view for copying it from. We’re looking for a similiar requirement or conditions to suggest a preferences for any of three implementations.
Two of the implementations will be familiar analogues of the last post: parameterizing an existing controller method, or creating a specialized method. The third will be a solution that delegates the whole matter to the client, because we’ll have a whole controller and action just for carrying out the action.
Circling back to diagrams: as I mentioned, the abstract flow diagrams I tried to come up with don’t illustrate this as well as I hoped, so I have switched to “sequence diagrams” which have the benefit of focusing on a sequence of interactions in time.
Side effect by parameterized action
Since the side effect is optional we could add a conditional to the controller method that carries out the side effect when a parameter is supplied by the client.
What’s nice about this is that we keep a single interface for creating
new Wat
resources, and the differences in creating them, are matters
of the User interface. Error handling logic has been left to the
imagination of the reader.
What’s less nice, maybe, is that it’s up to the client to supply this parameter.
For every “golden path” there are several “stoney paths” that have to be considered. What are the impacts if the client spuriously includes or excludes the parameter? Obviously, the side effect happens or does not happen spuriously as a consequence, but applications and their users are depending on systems informing them appropriately, so the analysis should extend to the organization, people, and processes that depend on the application.
However, the parameter doesn’t have to be someting explicitly supplied
by the user, as in the example–it could be what I’m going to call
“implicit” by which I mean, it’s still part of the request, just not
any part the user “controls”. In this example, where Wat copy
has
it’s own view, the controller action could check request.referer
to
determine if it should carry out the side effect. This mitigates the
issue somewhat.
If it seems a little eyebrow raising to use implicit parameters like this, I think that’s a good instinct. However most applications do this kind of thing for access control, and maybe the side effect is something that might be fairly considered “foundational” in the same way. We’ll have to come back to that.
If the server is more of an API and not rendering the views, you might feel more constrained to use explicit parameters. If it’s more appropriate or important for the side effect to be managed on the server then we may wish to consider using a specialized action.
Side effect by specialized action
While I’m pretty comfortable creating new safe actions/views (per the last post), I’m less confident about creating new unsafe actions, but if the side effect really needs to be managed by the server, or if it complicates methods inappropriately, a new action can be set up for it.
What I came up with, is that the copy view will submit to a new “member” route:
POST /wats/{wat_id}/create_copy
and a new controller method will create the copy and invoke the side effect. The sequence looks like this:
What’s nice about this is that it’s really simple (again, error handling logic has been left to the imagination of the reader).
The client still has to choose the right interface, but, in the case
of the copy
feature, it lines up with the two views:
new
uses the basicPOST /wats
andcopy
uses the newPOST /wats/{wat_id}/create_copy
.
I want to briefly examine what makes me uncomfortable about this
implementation. The basic unsafe actions – create
, update
,
delete
– do four things:
- check their parameters
- carry out their action
- carry out any side effects
- redirect to a view (alternatively, render a response for an API client)
Controller methods should be marshalling resources and making certain kinds of decisions about how to fulfill the request. It’s not at all obvious to me when we should consider making a new controller action to fulfill a request. Any aspect of the four items could reasonably be subject to a parametric conditions.
It’s only because of the requirement that the side effect be mediated by the server, and as we’ve seen, that’s not really a strong distinction. Clients have to be configured to use either appropriate parameters or appropriate interfaces in both cases.
Alternate names
It was recently suggested to me that a good alternate name for the “copy” view would be “template”. So,
GET /wats/{id}/template
This would free up “copy” to be used as the name of the action, so
instead of create_copy
it would be, just copy
and the request
would be:
POST /wats/{id}/copy
Although, I like the semantics of this a lot, I’m going to stick with
calling the action create_copy
. For one thing, I expect to get into
examples which may not have good semantic names. If you only have one
word for the action and it will serve as a view, I think a convention
of naming specialized actions after one of the basic actions like
this:
create_*
update_*
destroy_*
will probably work well to corral our interfaces into ones that better abide by REST constraints.
Side effect by delegated client action
If the side effect is something that can be independently invoked,
then delegating the invokation to the client may be justified. This
could allow us to use the standard create
interface for copying the
objects while the side effect interface is used when the client needs
that side effect.
What’s nice about this is that it relies on abilities client already has, so the development effort lies in configuring the client to use it. I did add some error handling for this one to give a flavor of the kind of planning one would actually have to do.
However, with this solution great responsibility falls upon the client. This raises a lot of questions about how clients are configured, where business logic is defined, and how it is communicated to the entities that carry it out. We’ll have to come back to this too.
Summary and conclusion
Reviewing the implementations and commentary I see a kind of decision tree emerging from some of the questions:
- Who should be responsible for triggering the side effect?
- server: maybe use specialized action
- client:
- can the side effect be triggered independently?
- yes: create a separate action for the effect
- no: parameterize an existing action
- can the side effect be triggered independently?
Other questions asked:
- How does a side effect feature:
- effect representation of resources?
- effect user interaction with resources?
- How shall spurious uses of the interfaces and parameters be handled?
- For parameterized interfaces, what would suggest that “implicit”
rather than “explicit” parameters be used?
- nomenclature of “implicit/explicit” seems weird, but here’s what I
mean by them:
- “explicit” parameters are options the user controls, examples:
- query params
- request body
- “implicit” parameters are more like session states the user
doesn’t control, examples:
- referer (or other) header
- client or user information encoded in an access token
- “explicit” parameters are options the user controls, examples:
- nomenclature of “implicit/explicit” seems weird, but here’s what I
mean by them:
- How can we discover useful semantics for the interfaces or
parameters?
- (Note to self, I should work on my “Lexicode” project some more.)
- How can we configure and/or communicate business level requirements to clients?
That’s a lot of future posts!
Writing this has been really hard. One thing I keep stumbling over in the revision process has been what I’m calling the “nomenclature knot”. There’s a lot of jargon, and even though I’m trying to be careful with it, I may not be using it as well as I should, so I think I’m going to take a crack at that next.
This is complicated, nuanced stuff, if I can’t come up with simple language for writing about it, I will have to strive to be clear about the language I use, as coherent about it as I can, and provide context if consistency is a problem–for example if two domains share some terminology.
We’ll see what I come up with.