11

I'm developing a new service in a micro-services environment. This is a REST service. For simplicity, let's say that the path is: /historyBooks

And the POST method for this path creates a new history book.

Let's assume that a history book covers one or more eras in history.

For brevity, let's assume we have only the following eras of human history:

  • Ancient
  • Post Classical
  • Modern

In my code, I'd like to represent them in an enum.

The method's body (the payload) is in JSON format, and should include a field name eras. This field is a list of era values, that this book covers.

The body may look like:

{
  "name": "From the cave to Einstein - a brief history review",
  "author": "Foo Bar",
  "eras": ["Ancient", "Post Classical", "Modern"]
}

In this specific service, the business logic is:
If no eras provided in the input, then this book is considered to cover all eras.

In the API review, a suggestion was made:
Include another value, ALL, for the eras enum, to explicitly indicate that all of the eras are covered.

I think it has some pros and cons.

Pros:

Explicit input

Cons:

If two items in the list are provided, say ALL and Ancient - what will be taken from the application? I guess that ALL should override the other values, but that's new business logic.

If I run a query, for books that cover specific era(s), how would I represent a books that cover all eras? If ALL is also used for the output (using the same logic), then it's the consumer's responsibility to interpret ALL as ["Ancient", "Post Classical", "Modern"].

My Question

I think that having the new ALL causes more confusion than not having it at all.

What do you think? Would you add this ALL value or keep your API without it?

1
  • 7
    What happens if you decide you'd like to add the atomic era? Do the books that cover "all" eras magically get new content? Do you start lying about the books' contents? Do you go through and update everything? That could be nontrivial if some books actually do already have content about the atomic era.
    – 8bittree
    Commented Sep 19, 2017 at 21:28

7 Answers 7

13

Depends on whether your available eras are available to the calling application. Presumably they are so the user can select what they're interested in. If that's the case then it's a front-end issue to supply an "ALL" option. If it were me, that would have it send a list of all eras rather than an "all" option. If not, then you need an "ALL" option and run the risk then that the front end will get things back from an era it won't understand.

As you point out, ALL with other options means you can get conflicting requests. One further consideration is that you can pass "ALL" then any other enums become exclusions rather than inclusions, e.g. "ALL", "Ancient" means "Everything except the Ancients". That makes some kind of sense but obviously the UI then has to reflect that which may just be over complex.

TL;DR; Mostly "ALL" is a UI nicety & you can achieve the same thing at the service level with no ambiguity so don't do it.

6

If no eras provided in the input, then this book is considered to cover all eras.

This, and also having a special 'ALL' case, are bad IMHO - your APIs should be explicit where possible. Having special cases like 'nothing actually means everything' means that anyone who consumes your API also has to know all your special cases, or, as with the 'ALL' case, lead to requests where the intent and/or outcome are ambiguous.

Special cases also often lead to more complex code both in terms of validating whether the user input is valid or not, and also handling the request correctly.

1

For categorical variables, I'd avoid putting a wildcard "all" at the same level.

It sounds like you have a two-level search criterion for time period:

  1. boolean, is_selective?
  2. set, disjunction of specific categories

Caller may specify (false, ignored) or (true, {'ancient', 'modern'}).

Or you could choose to interpret empty set as the wildcard, it denotes Not Selective.

For your specific case, it sounds like you have a continuous variable, the year, that is discretized to a few well-known values, and what you really want is to do interval arithmetic. So your queries would accept a (start, end) range of years, and enums could conveniently supply such attributes.

1

tl;dr
For your actual question – concerning the local data structures in the implementation – I agree with your conclusion: Avoid an all eras special value because it mostly adds complexity and opportunities to make mistakes. However, especially the end user UI and maybe the serialized payload data may be different stories.

Local data structures

Let’s look at this from a type system perspective. Basically an enum is a type that defines a disjunct set of specific categories (enumerators), as already pointed out in the answer by @J H. A variable of the enum type holds exactly one of those categories. If you want to represent a collection of enumerator values, you need a second type:

// C++-inspired pseudo-code
enum Era { ancient, post_classical, modern };
using Eras = Collection<Era>;

An all enumerator is a no-go because it mashes those two types together. It’s not a single category, but a collection of all possible categories.

On a strictly practical level dealing with any kind of special value complicates the implementation – and so raises the likelyhood of errors – because either you have to special-case it everywhere or unpack it to match its actual meaning. Oh, and what about an explicit collection of all possible enumerators. When and where should this be collapsed to the special all value? Should it be collapsed at all?

My preferred architecture is to not have any representation of all eras in the code. That includes the all enumerator, but also an empty collection meaning all eras. It’s exactly the same special case, just in a different disguise.

If two items in the list are provided, say ALL and Ancient - what will be taken from the application? [...]

I’d specify in the API spec that the all eras marker must always appear on its own because having something on top of it does not make sense. Then I’d reject this as a contract violation. But it’s a nice example of how all eras complicates both the implementation and the business logic.

The main problem is that you’re forced to specify rules for converting between the special value and its underlying meaning. And then you and everybody else using the API have to implement those rules correctly. The cynic in me tells me that it’s not a question of if someone will get it wrong, but simply when.

Serialized Data (JSON)

Originally I had a whole section here about modifying vs. read-only queries and different roles for the "eras" list. But in the end I deleted it because KISS. The rules for enum/collection are not unreasonable for the JSON data, so keeping things consistent is the simplest solution.

UI for the end user

I assume there will be a data transformation anyway between the UI and the underlying data structures, most likely because you have some kind of MVC-ish architecture. So, use whatever is most convenient and intuitive for the user. In his answer @Markus gave a great example with the different selection options for the days of the week.

1

I think that having the new ALL causes more confusion than not having it at all.

Yes.

If you think from a collection perspective: you have a whole collection of something. And everytime you only want a subset of that collection, you have to provide some kind of filtercondition, when an element is considered an element of this subset. And ALL is the case, when no filter is applied, not "the filtercondition is ALL".

If no eras provided in the input, then this book is considered to cover all eras.

I would turn it upside down: This book covers no specific era.

This is also consistent from a query perspective:

If no era is selected, all books are returned (including this with an empty era field); and when an era is selected, only books with the selected era are returned - retrieving all books from a special era plus general ones would be somehow be unexpected.

The only point, where I would add the ALL - category is for the user's convenience, because it may be more irritating having "Nothing" selected instead of "Everything".

0

I've recently implemented a basic scheduler and as @LoztInSpace already mentioned most of it is UI sugar.

The user interface needs to be absolutely clear in what the user will achieve when selecting one of the options.

In my case I have weekdays to select. In addition to "All" I added "Working days" and "Weekend" as options. Selecting each option will include it in a later use and you will have to manually deselect any days you don't want included.

Technically this means if the user selects "Weekdays" the UI will have five checkboxes ticked and the enum will use the "Working days" value. If one of the checkboxes is unticked, the "Working days" value is replaced with a or-ed bit map of the remaining four days.

Do not use one part of the options to inlcude everything and at the same time to exclude something to avoid the conflicts and make sure the UI makes sense to the user.

I think a good orientation is that using and "All" option makes sense if the user can easily grasp the concept what "All" includes (all days in a week) or if is rather unimportant (search all categories on amazon)

0

I like the idea of explicitly carry a ALL enum, but I would rather set them as flags

const enum = {
    ALL: { value: 0 },
    ANCIENT: { name: 'Ancient', value: 1 },
    POST_CLASSICAL: { name: 'Post Classical', value: 2 },
    MODERN: { name: 'Modern', value: 4 }
}

Using a library like flagon or manually playing with bitwise operations when all values are selected, you use ALL instead.

Keep in mind that the enum's values should always be the double of their predecessor. And that for sanity check you should never remove an enum, though you may disable it, adjusting your code to always count the disabled ones as part of ALL.

Though I would have a NONE flag instead and let the client decide what to do when NONE is used, in case the business changes later.

If this implementation is taken, instead of passing the enuns around, you'd pass the sum of the selected values instead.

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