Posts Tagged “routing”

Wednesday, August 29, 2018
  A Tour of myPrayerJournal: The API

NOTES:

  • This is post 4 in a series; see the introduction for all of them, and the requirements for which this software was built.
  • Links that start with the text “mpj:” are links to the 1.0.0 tag (1.0 release) of myPrayerJournal, unless otherwise noted.

Now that we have a wonderful, shiny, reactive front end, we need to be able to get some data into it. We'll be communicating via JSON between the app and the server. In this post, we'll also attempt to explain some about the F# language features used as part of the API.

The Data

The entities are defined in Data.fs (mpj:Data.fs). We'll dig into them more fully in the “data store” post, but for now, we'll just focus on the types and fields. We have four types: History (lines 33-39), Note (lines 67-71), Request (lines 94-110), and JournalRequest (lines 153-173). A Request can have multiple Notes and History entries, and JournalRequest is based on a view that pulls these together and computes things like the current text of the request and when it should be displayed again.

We apply no special JSON transformations, so the fields in these record types are the properties in the exported JSON.

The URLs

To set the API apart from the rest of the URLs, they all start with /api/. Request URLs generally follow the form request/[id]/[action], and there is a separate URL for the journal. Line 54 in Program.fs (mpj:Program.fs) has the definition of the routes. We used Giraffe's Token Router instead of the traditional one, as we didn't need to support any URL schemes it doesn't. The result really looks like a nice, clean “table of contents” for the routes support by the API.1

We aren't done with routes just yet, though. Let's take a look at that notFound handler (mpj:Handlers.fs); it's on line 27. Since we're serving a SPA, we need to return index.html, the entry point of the SPA, for URLs that belong to it. Picture a user sitting at https://prayerjournal.me/journal and pressing “Refresh;” we don't want to return a 404! Since the app has a finite set of URL prefixes, we'll check to see if one of those is the URL. If it is, we send the Vue app; if not, we send a 404 response. This way, we can return true 404 responses for the inevitable hacking attempts we'll receive (pro tip, hackers - /wp-admin/wp-upload.php does not exist).

Defining the Handlers

Giraffe uses the term “handler” to define a function that handles a request. Handlers have the signature HttpFunc -> HttpContext -> Task<HttpContext option> (aliased as HttpHandler), and can be composed via the >=> (“fish”) operator. The option part in the signature is the key in composing handler functions. The >=> operator creates a pipeline that sends the output of one function into the input of another; however, if a function fails to return a Some option for the HttpContext parameter, it short-circuits the remaining logic.2

The biggest use of that composition in myPrayerJournal is determining if a user is logged in or not. Authorization is also getting its own post, so we'll just focus on the yes/no answer here. The authorized handler (line 71) looks for the presence of a user. If it's there, it returns next ctx, where next is the next HttpFunc and ctx is the HttpContext it received; this results in a Task<HttpContext option> which continues to process, hopefully following the happy path and eventually returning Some. If the user is not there, though, it returns the notAuthorized handler, also passing next and ctx; however, if we look up to line 67 and the definition of the notAuthorized handler, we see that it ignores both next and ctx, and returns None. However, notice that this handler has some fish composition in it; setStatusCode returns Some (it has succeeded) but we short-circuit the pipeline immediately thereafter.

We can see this in use in the handler for the /api/journal endpoint, starting on line 137. Both authorize and the inline function below it have the HttpHandler signature, so we can compose them with the >=> operator. If a user is signed in, they get a journal; if not, they get a 403.

When a handler is expecting a parameter from a route, the handler's signature is a bit different. The handler for /api/request/[id], on line 246, has an extra parameter, reqId. If we look back in Program.fs line 64, we see where this handler is assigned its route; and, if you compare it to the route for /api/journal on line 59, you'll see that it looks the same. The difference here is the expectations of route (for the journal) vs. routef (for the request). route expects no parameter, but routef will extract the parameters, Printf style, and pass them as parameters that precede the HttpHandler signature.

Executing the Handlers

myPrayerJournal has GET, POST, and PATCH routes defined. We'll look at representative examples of each of these over the next few paragraphs.

For our GET example, let's return to the Request.get handler, starting on line 246. Once we clear the authorize hurdle, the code attempts to retrieve a JournalRequest by the request ID passed to the handler and the user ID passed as part of the request. If it exists, we return the JSON representation of it; if not, we return a 404. Note the order of parameters to the json result handler - it's json [object] next ctx (or json [object] HttpHandler). We also defined an asJson function on line 75; this flips the parameters so that the output object is the last parameter (asJson next ctx [object]), enabling the flow seen in the journal handler on line 137.

For our POST example, we'll use Request.addNote, starting on line 218. It checks authorization, and to make sure the request exists for the current user. If everything is as it should be, it then uses Giraffe's BindJsonAsync<'T> extension method to create an instance of the expected input form. Since the handler doesn't have a place to specify the expected input object, this type of model binding cannot happen automatically; the upside is, you don't waste CPU cycles trying to do it if you don't need it. Once we have our model bound, we create a new Note, add it, then return a 201 response.

PATCH handlers are very similar; Request.snooze is one such handler, starting on line 291. The flow is very similar as the one for Request.addNote, except that we're updating instead of adding, and we return 204 instead of 201.

Configuring the Web Server

Giraffe enables Suave-like function composition on top of Kestrel and the ASP.NET Core infrastructure. Rather than using the Startup class, myPrayerJournal uses a functional configuration strategy. The calls are in Program.fs starting on line 115; there are lots of other guides on how to configure ASP.NET Core, so I won't say too much more about it.

Notice, though, line 31. Earlier, we discussed the >=> operator and how it worked. This is an example of the >> composition operator, which is part of F#. For functions whose output can be run directly into the next function's input, the >> operator allows those functions to be composed into a single function. If we were to look at the signature of the composed function within the parentheses, its signature would be string -> unit; so, we pass the string “Kestrel” to it, and it runs through and returns unit, which is what we expect for a configuration function. Here's how that breaks down:

  • ctx.Configuration.GetSection's signature is string -> IConfigurationSection
  • opts.Configure's signature is IConfiguration -> KestrelConfigurationLoader (IConfigurationSection implements IConfiguration)
  • ignore's signature is 'a -> unit

If this still doesn't make sense, perhaps this will help. The Configure.kestrel function could also have been written using the |> piping operator instead, with the equivalent code looking like:

  let kestrel (ctx : WebHostBuilderContext) (opts : KestrelServerOptions) =
    ctx.Configuration.GetSection "Kestrel"
    |> opts.Configure
    |> ignore

 

That concludes our tour of the API for now, though we'll be looking at it again next time, when we take a deeper dive into authentication and authorization using Auth0.


1 While we tried to follow REST principles in large part, the REST purists would probably say that it's not quite RESTful enough to claim the name. But, hey, we do use PATCH, so maybe we'll get partial credit…

2 Scott Wlaschin has a great post entitled “Railway Oriented Programming” that explains this concept in general, and the fish operator specifically. Translating his definition to Giraffe's handlers, the first function is switch1, the next parameter is switch2, and the HttpContext is the x parameter; instead of Success and Failure, the return type utilizes the either/or nature of an option being Some or None. If you want to understand what makes F# such a great programming model, you'll spend more time on his site than on The Bit Badger Blog.

Categorized under , ,
Tagged , , , , , , , , , , , , , , , , , , ,

Saturday, August 25, 2018
  A Tour of myPrayerJournal: The Front End

NOTES:

  • This is post 2 in a series; see the introduction for all of them, and the requirements for which this software was built.
  • Links that start with the text “mpj:” are links to the 1.0.0 tag (1.0 release) of myPrayerJournal, unless otherwise noted.

Vue is a front-end JavaScript framework that aims to have very little boilerplate and ceremony, while still presenting a componentized abstraction that can scale to enterprise-level if required1. Vue components can be coded using inline templates or multiple files (splitting code and template). Vue also provides Single File Components (SFCs, using the .vue extension), which allow you to put template, code, and style all in the same spot; these encapsulate the component, but allow all three parts to be expressed as if they were in separate files (rather than, for example, having an HTML snippet as a string in a JavaScript file). The Vetur plugin for Visual Studio Code provides syntax coloring support for each of the three sections of the file.

Layout

Using the default template, main.js is the entry point; it creates a Vue instance and attaches it to an element named app. This file also supports registering common components, so they do not have to be specifically imported and referenced in components that wish to use them. For myPrayerJournal, we registered our common components there (mpj:main.js). We also registered a few third-party Vue components to support a progress bar (activated during API activity) and toasts (pop-up notifications).

App.vue is also part of the default template, and is the component that main.js attaches to the app elements (mpj:App.vue). It serves as the main template for our application; if you have done much template work, you'll likely recognize the familiar pattern of header/content/footer.

This is also our first look at an SFC, so let's dig in there. The top part is the template; we used Pug (formerly Jade) for our templates. The next part is enclosed in script tags, and is the script for the page. For this component, we import one additional component (Navigation.vue) and the version from package.json, then export an object that conforms to Vue's expected component structure. Finally, styles for the component are enclosed in style tags. If the scoped attribute is present on the style tag, Vue will generate data attributes for each element, and render the declared styles as only affecting elements with that attribute. myPrayerJournal doesn't use scoped styles that much; Vue recommends classes instead, if practical, to reduce complexity in the compiled app.

Also of note in App.js is the code surrounding the use of the toast component. In the template, it's declared as toast(ref='toast'). Although we registered it in main.js and can use it anywhere, if we put it in other components, they create their own instance of it. The ref attribute causes Vue to generate a reference to that element in the component's $refs collection. This enables us to, from any component loaded by the router (which we'll discuss a bit later), access the toast instance by using this.$parent.$refs.toast, which allows us to send toasts whenever we want, and have the one instance handle showing them and fading them out. (Without this, toasts would appear on top of each other, because the independent instances have no idea what the others are currently showing.)

Routing

Just as URLs are important in a regular application, they are important in a Vue app. The Vue router is a separate component, but can be included in the new project template via the Vue CLI. In App.vue, the router-view item renders the output from the router; we wire in the router in main.js. Configuring the router (mpj:router.js) is rather straightforward:

  • Import all of the components that should appear to be a page (i.e., not modals or common components)
  • Assign each route a path and name, and specify the component
  • For URLs that contain data (a segment starting with :), ensure props: true is part of the route configuration

The scrollBehavior function, as it appears in the source, makes the Vue app mimic how a traditional web application would handle scrolling. If the user presses the back button, or you programmatically go back 1 page in history, the page will return to the point where it was previously, not the top of the page.

To specify a link to a route, we use the router-link tag rather than a plain a tag. This tag takes a :to parameter, which is an object with a name property; if it requires parameters/properties, a params property is included. mpj:Navigation.vue is littered with the former; see the showEdit method in mpj:RequestCard.vue for the structure on the latter (and also an example of programmatic navigation vs. router-link).

Components

When software developers hear “components,” they generally think of reusable pieces of software that can be pulled together to make a system. While that isn't wrong, it's important to understand that “reusable” does not necessarily mean “reused.” For example, the privacy policy (mpj:PrivacyPolicy.vue) is a component, but reusing it throughout the application would be… well, let's just say a “sub-optimal” user experience.

However, that does not mean that none of our components will be reused. RequestCard, which we referenced above, is used in a loop in the Journal component (mpj:Journal.vue); it is reused for every request in the journal. In fact, it is reused even for requests that should not be shown; behavior associated with the shouldDisplay property makes the component display nothing if a request is snoozed or is in a recurrence period. Instead of the journal being responsible for answering the question "Should I display this request?", the request display answers the question "Should I render anything?". This may seem different from typical server-side page generation logic, but it will make more sense once we discuss state management (next post).

Looking at some other reusable (and reused) components, the page title component (mpj:PageTitle.vue) changes the title on the HTML document, and optionally also displays a title at the top of the page. The “date from now” component (mpj:DateFromNow.vue) is the most frequently reused component. Every time it is called, it generates a relative date, with the actual date/time as a tool tip; it also sets a timeout to update this every 10 seconds. This keeps the relative time in sync, even if the router destination stays active for a long time.

Finally, it's also worth mentioning that SFCs do not have to have all three sections defined. Thanks to conventions, and depending on your intended use, none of the sections are required. The “date from now” component only has a script section, while the privacy policy component only has a template section.

Component Interaction

Before we dive into the specifics of events, let's look again at Journal and RequestCard. In the current structure, RequestCard will always have Journal as a parent, and Journal will always have App as its parent. This means that RequestCard could, technically, get its toast implementation via this.$parent.$parent.toast; however, this type of coupling is very fragile2. Requiring toast as a parameter to RequestCard means that, wherever RequestCard is implemented, if it's given a toast parameter, it can display toasts for the actions that would occur on that request. Journal, as a direct descendant from App, can get its reference to the toast instance from its parent, then pass it along to child components; this only gives us one layer of dependency.

In Vue, generally speaking, parent components communicate with child components via props (which we see with passing the toast instance to RequestCard); child components communicate with parents via events. The names of events are not prescribed; the developer comes up with them, and they can be as terse or descriptive as desired. Events can optionally have additional data that goes with it. The Vue instance supports subscribing to event notifications, as well as emitting events. We can also create a separate Vue instance to use as an event bus if we like. myPrayerJournal uses both of these techniques in different places.

As an example of the first, let's look at the interaction between ActiveRequests (mpj:ActiveRequests.vue) and RequestListItem (mpj:RequestListItem.vue). On lines 41 and 42 of ActiveRequests (the parent), it subscribes to the requestUnsnoozed and requestNowShown events. Both these events trigger the page to refresh its underlying data from the journal. RequestListItem, lines 67 and 79, both use this.$parent.$emit to fire off these events. This model allows the child to emit events at will, and if the parent does not subscribe, there are no errors. For example, AnswerdRequests (mpj:AnsweredRequests.vue) does not subscribe to either of these events. (RequestListItem will not show the buttons that cause those events to be emitted, but even if it did, emitting the event would not cause an error.)

An example of the second technique, a dedicated parent/child event bus, can be seen back in Journal and RequestCard. Adding notes and snoozing requests are modal windows3. Rather than specifying an instance of these per request, which could grow rather quickly, Journal only instantiates one instance of each modal (lines 19-22). It also creates the dedicated Vue instance (line 46), and passes it to the modal windows and each RequestCard instance (lines 15, 20, and 22). Via this event bus, any RequestCard instance can trigger the notes or snooze modals to be shown. Look through NotesEdit (mpj:NotesEdit.vue) to see how the child listens for the event, and also how it resets its state (the closeDialog() method) so it will be fresh for the next request.

 

That wraps up our tour of Vue routes and components; next time, we'll take a look at Vuex, and how it helps us maintain state in the browser.


1 That's my summary; I'm sure they've got much more eloquent ways to describe it.

2 ...and kinda ugly, but maybe that's just me.

3 Up until nearly the end of development, editing requests was a modal as well. Adding recurrence made it too busy, so it got to be its own page.

Categorized under , ,
Tagged , , , , , , , , , , , ,