Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preliminary guide drafts #1

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Add preliminary guide drafts #1

wants to merge 10 commits into from

Conversation

andrewcroome
Copy link
Collaborator

@andrewcroome andrewcroome commented Oct 9, 2022

This adds some preliminary drafts, covering an intro, an "Application structure" section (covering containers and dependency injection, providers, and slices) and the beginnings of a "HTTP handling" section (that would cover routing, actions and rack middleware).

Nothing at all set in stone - all very much work in progress still and up for discussion and input.

docs
├── intro.md 
├── application-structure
│   ├── containers.md
│   ├── providers.md
│   └── slices.md
├── http-handling
│   ├── actions.md
│   ├── rack-middleware.md
│   └── routing.md

To view this locally:

npm install
npm start
open http://localhost:3000/

Includes:

docs
├── intro.md 
├── application-structure
│   ├── containers.md
│   ├── providers.md
│   └── slices.md
├── http-handling
│   ├── actions.md
│   ├── rack-middleware.md
│   └── routing.md
@timriley
Copy link
Member


When our application boots, Hanami will automatically create __instances__ of these components and register them in its __app container__, under a key based on their Ruby namespace.

For example, an instance of our `NotificationsService::Emails::Welcome::Operations::Send` class will be registered under the key `"emails.welcome.operations.send"`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to use examples with shorter names and flatter structure. Could we rename this example app to Bookshelf (especially that this is what we already use in the Getting Started) and use flatter namespace like Emails::Welcome::Send? or even Operations::SendWelcomeEmail?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, good call @solnic. I'll adjust.

Copy link
Member

@timriley timriley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @andrewcroome! I am loving reading these guides!

I want to give you feedback as early as possible, so I thought I'd just do it progressively while I read.

Here's my thoughts on containers.md to get things started :)


# Containers and dependencies

In Hanami, the application code you add to your `/app` directory is automatically organised into a container. This container forms the basis of a depenency injection system, in which the dependencies of the components you create are provided to them automatically.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say that the container forms the basis of a "component management system", of which dependency injection is one part.

Defining what a "component" is here (as well as a "dependency") would be useful since we'll likely want to refer to "component" across the rest of the guides.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can also rely on the "Dependency injection" section below to introduce the idea of dependencies and DI as a concept.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thanks, yes. I've tweaked this intro to put that component management system first, and had a shot at explaining what a component is and being clearer with a definitely of dependency et al if you're able to take another read @timriley.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timriley maybe let's wait until it's in Hugo for more feedback though.


Let's take a look at how this works in practice.

Imagine we're building a Bookshelf notifications service for sending notifications to users of the Bookshelf platform. After running `hanami new notifications_service`, our first task is to send welcome emails. To achieve this, we want to provide a `POST /welcome-emails` action that will send a welcome email, probably via a send welcome operation, which in turn will want to render our email in both html and plain text.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Imagine we're building a Bookshelf notifications service for sending notifications to users of the Bookshelf platform. After running `hanami new notifications_service`, our first task is to send welcome emails. To achieve this, we want to provide a `POST /welcome-emails` action that will send a welcome email, probably via a send welcome operation, which in turn will want to render our email in both html and plain text.
Imagine we're building a Bookshelf notifications service for sending email notifications to users of the Bookshelf platform. After running `hanami new notifications_service`, our first task is to send welcome emails. To achieve this, we want to provide a `POST /welcome-emails` action that will send a welcome email, probably via a send welcome operation, which in turn will want to render our email in both html and plain text.

"Notifications" can mean so many things these days.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second thought here: we've gone from creating a single "bookshelf" app in the first page of the guide to creating another "notifications_service" app in the second page of the guide. This feels like an undesirable amount of whiplash, and I don't think we want to go "all in on microservices" to our users right off the bat (or ever).

It would be good if our guide could focus on building up a single app only, especially since Hanami also provides the tools to make that app more modular over time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, excellent point, I've adjusted so there's just a Bookshelf app.

└── text.rb
```

When our application boots, Hanami will automatically create __instances__ of these components and register them in its __app container__, under a key based on their Ruby namespace.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Ruby namespace" -> "class name" ?


When our application boots, Hanami will automatically create __instances__ of these components and register them in its __app container__, under a key based on their Ruby namespace.

For example, an instance of our `NotificationsService::Emails::Welcome::Operations::Send` class will be registered under the key `"emails.welcome.operations.send"`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @solnic mentioned this somewhere? But it'd be good if we could use slightly shallower names for our guide - this quite deep name spacing could be a little confronting (even if it is how things tend to play out in practice).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone for @solnic's Operations::SendWelcomeEmail suggestion and simplified the examples by removing settings too. Happy to adjust further if needed.

Comment on lines 138 to 139
attr_reader :text
attr_reader :html
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO these are unnatural names for these dependencies. Could we make them render_text or text_renderer?

(Depending on how things feel, we could also use this as a way to subtly introduce the local_name: "name.of.dep" syntax for Deps in the following section)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I've another thought here, in that the text and HTML renderers might actually be reasonable to keep as concrete references to classes, but that would in part be contingent on how they're designed as libraries, and whether the renderers themselves need to access any other facets of the app. Given how this is a design decision that could go either way, I think it makes sense for the simplicity of this example to presume that all of these could be injected reps)


Another alternative for classes you do not want to be registered in your container is to place them in `/lib`.

If you have a whole class of objects that shouldn't be placed in your container, you can configure your Hanami application (or slice) to exclude an entire directory from auto registration by adjusting its `no_auto_register_paths` configuration.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"whole class of objects" -> "whole category of objects"

(we're already talking about ruby classes and files here, so probably helpful to avoid confusing the terms?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BTW, I actually thought this sentence was adding more detail to the one above about /lib. Perhaps it would be better to reverse the order:

  • magic comment
  • no_auto_register_paths
  • lib

Also, on the lib stuff, it'd probably be useful to explain that lib/[app_namespace]/ is still autoloaded? And that the rest of lib is still available to requiere explicitly if needed. (Maybe that's too much detail here; you let me know what you think).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yep, thanks - changed that order and added a breakout box with that require and autoloading explanation.


## Container behaviour: prepare vs boot

Hanami supports a **prepared** state and a **booted** state.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be helpful to make it clear that these are states on the Hanami app.


### Hanami.prepare

When you call `Hanami.prepare` (or use `require "hanami/prepare"`) Hanami will make its app and slices available, but components within containers will be **lazily loaded**.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning! Mention of "slices", but we haven't introduced that concept yet.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good call, I've removed any reference to slices anywhere before we get to Slices in the guides (which at this point is only in the Slices doc).


This is useful for minimizing load time. It's the default mode in the Hanami console and when running tests.

It can also be very useful when running Hanami in serverless environments where boot time matters, such as on AWS Lambda, as Hanami will instantiate only the components needed to satisfy a particular web request or operation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH I would leave this out for now. Right now we're not focused on the serverless use case. And when we get to that, I'd like to do a lot more than just say "prepare instead of boot the app".


### Hanami.boot

When you call `Hanami.boot` (or use `require "hanami/boot"`) Hanami will go one step further and **eagerly load** all components in all containers up front.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning! Mention of "all containers" even though we've only talked about one. Given we're at this early stage of the guide, can we hide away the notion of multiple slices/containers, saving those for when we get to that particular part of the guide?

Copy link
Member

@timriley timriley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Providers feedback!


- you need to set up a dependency that requires non-trivial configuration (often a third party library, or some library-like code in your `lib` directory)
- you want to take advantage of provider lifecycle methods (prepare, start and stop)
- you want to share a component across both your app container and the containers of all your [slices](/docs/application-architecture/slices).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This third point can actually be done for any component, provider-registered or otherwise, so I think we should remove this.

What I think we could add is something like this:

  • you want to register a specific instance of an object as a component, and have that very same instance be available as a dependency

(or similar; feel free to massage the words. I reckon this should be either the first or second item. The current first item feels like a specialisation of this case, tbh, so perhaps it makes sense to lead with this new one).


App-level providers should be placed in the `config/providers` directory. Slices can have their own providers also, placed in `slices/my_slice/providers`.

Here's an example provider for that registers an email client in the app container, using an imagined third-party Acme Email service.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On first read I found the term "email client" to be a little odd here, since email clients in ordinary usage tend to mean apps like Mail.app, Thunderbird, etc.

When reading the sentence in full, it all makes sense, but I wonder if we could avoid the potential initial confusing by tweaking it to something like "registers a client for an imagined third-party Acme Email delivery service."

Thoughts?

Copy link
Collaborator Author

@andrewcroome andrewcroome Oct 23, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tweaked, thanks! I've kinda tried to connect email_client here in providers with email_client from the container guide and have called that out a bit more at the bottom of the container guide. Doesn't have to be this way though if anyone has thoughts for a better example.

end
```

The above provider initializes an instance of Acme's email client, providing an api key from the application's setting as well as a default from address, then registers the client in the app container with the key `"email_client"`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The above provider initializes an instance of Acme's email client, providing an api key from the application's setting as well as a default from address, then registers the client in the app container with the key `"email_client"`.
The above provider initializes an instance of Acme's email client, providing an API key from the application's settings as well as a default from address, then registers the client in the app container with the key `"email_client"`.

include Deps["email_client", "settings"]

def call(name:, email_address:)
return unless settings.email_sending_enabled
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it doesn't serve any purpose for this page in the guide. Could we remove? That might also be helpful in that it removes the settings dependency and can help the reader focus on email_client instead.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed, thank you!

end
```

Every provider has a name (`Hanami.app.register_provider(:my_provider_name)`) and registers _one or more_ related components with the relevant container. Registered items are not limited to objects - they can be classes too.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The truth is that this could actually be "zero or more".

In this way, providers become a way to trigger code and make side effects during app boot or when resolving any container key with the provider's name as its base namespace segment.

This could be useful for e.g. third party gems that require global configuration, but provide nothing useful to otherwise register in the container.

This does feel like an "advanced" case, however. Do you think it fits anywhere at all with the story you're telling here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, right - does feel a bit advanced at this point. I've adjusted to say usually.


The above provider initializes an instance of Acme's email client, providing an api key from the application's setting as well as a default from address, then registers the client in the app container with the key `"email_client"`.

The registered dependency can now be used in app components, via `include Deps["email_client"]`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The registered dependency can now be used in app components, via `include Deps["email_client"]`:
The registered component can now become a dependency for other app components, via `include Deps["email_client"]`:

prepare do
require "3rd_party/db"

register "database", 3rdParty::DB.configure(target["settings"].database_url)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3rdParty is actually not a valid Ruby constant. Suggest we pick a different name to use in the code example here :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone with Acme::DB. We may want to adjust the dry-system docs too :)


`Hanami.boot` and `Hanami.shutdown` call `start` and `stop` respectively on each of the application’s registered providers.

Lifecycle transitions can be triggered directly by using `Hanami.app.container.prepare(:provider_name)`, `Hanami.app.container.start(:provider_name)` and `Hanami.app.container.stop(:provider_name)`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Lifecycle transitions can be triggered directly by using `Hanami.app.container.prepare(:provider_name)`, `Hanami.app.container.start(:provider_name)` and `Hanami.app.container.stop(:provider_name)`.
Lifecycle transitions can be triggered directly by using `Hanami.app.prepare(:provider_name)`, `Hanami.app.start(:provider_name)` and `Hanami.app.stop(:provider_name)`.

These methods are available on the app and slice classes themselves. In ordinary usage I'd like it if the user never has to touch .container. We should consider it mostly an internal implementation detail.

Also, is there any reason you shifted to the passive voice here? Could we go back to the more informal "You can also trigger the lifecycle steps for a provider by..."?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool - makes sense re the container being internal. I've adjusted.

end
```

Lifecycle steps will not run until a provider is required by another component, is started directly, or when the container finalizes as a result of Hanami booting.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"a provider is required by another component" is vague here.

Could we make it more specific, something like "a provider's registered components are accessed"?

Even that is still a little vague (IIRC I don't think we've introduced the concept of resolving from the container), but a little closer to the truth.

Would definitely be keen to see ideas from you here :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tweaked this somewhat but still not completely sure it's cracked in terms of the amount of vagueness.


Within a provider, the `target` method (also available as `target_container`) can be used to access the container (either the app container, or, if the provider is specific to a slice, the slice's container).

This is useful for accessing the application's settings or logger (via `target["settings]` and `target["logger"]`). It can also be used when a provider wants to ensure another provider has started before starting itself, via `target.start(:provider_name)`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also access any registered component via target, whether it's auto-registered or provider-registered. This might be worth incorporating into this paragraph somehow? I'd like to make it clear that it's not just "special" components that are available here.

Copy link
Member

@timriley timriley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback on slices.md! There's definitely some opportunity to expand on this one, but I wouldn't do it until we've covered the other areas of the guide first :)


In addition to the `app` directory, Hanami also supports organising your application code into **slices**.

You can think of slices as distinct modules of your application. A typical case is to use slices to separate your business domains (for example billing, accounting or admin) or to separate modules by feature or concern (api or search).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Business domain" and "feature or concern" feel too similar to each other, at least without further qualification. Perhaps we could tweak the latter to read as "feature or technical concern?"

Slices exist in the `slices` directory.
## Creating a slice

To create a slice, you can either create a new directory in `slices`:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder whether this and the hanami generate example should be in the other order. Is it better we guide people initially to our helpful built-in facilities (aka the "golden path") and only then follow up with more advanced methods?

I imagine @jodosha might have an opinion here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, I've adjusted this to preference mentioning the generator first.


A Hanami slice:

- has its own container (e.g. `API::Slice.container`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- has its own container (e.g. `API::Slice.container`)
- has its own container

Per my comments previously, I'd like .container to be for extreme power users only, so lets not mention this method in the getting started guide. (That the slice has a container is still valuable to mention! And we've done enough to make the slice itself embody that container through its own methods)


Like Hanami's `app` folder, components added to a Hanami slice are automatically organised into the slice's container.

For example, suppose our Bookshelf application, which catalogues international books, is in need of an API to return the name, flag, and currency of a given country. We might create a show action in our API slice (by adding the file manually or by running `bundle exec hanami generate action countries.show --slice api`), that looks something like:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm looking at the comma after the closing parenthesis here and wondering if we need it. If the parenthetical wasn't there, definitely not needed, given it is there, do we need it? </grammar confusion>

- can be prepared and booted independently of other slices
- can have its own slice-specific settings (e.g. `slices/api/config/settings.rb`)

## Slice containers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Half-way through reading this section I forgot that it was about slice containers, given that it became a de facto first time introduction to actions and routes (at least to a reader following the pages in the order you outlined in the PR description).

I wonder if there's an alternative high-level structure to this guide that sees the slices section more towards the end, after we've already covered actions and routing, with those sections demonstrating actions and routes within the app-level only.

With this, by the time we get to slices, everything should feel already familiar, so we can use e.g. actions in our examples without potentially distracting the reader.

This would also fit well with the idea of slices being an opt-in feature, since we will have shown how the whole framework can work without them before we show how things might change when they are introduced.

My personal feeling is that this would be a good way to go. I imagine @jodosha may think the same, but let's check anyway :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timriley Yeah, I think that does make sense. Let's look to have slices way down in the pecking order, so that routing and actions are familiar to folks first.

I am thinking eventually (at 2.1 release) it would be good to have something very much the current intro which walks through building CRUD for a book in a way that gives folks a good read through, and the guides we're creating here could be linked from that (in addition to being navigable through the nav obviously). As in the we could have comments like "Here the create action calls the create books operation, sourcing that component using the Deps mixin. For a guide to how that works, read Containers and components"


Images can be updated in one of two ways: the publisher of the book can sign in and upload a new image, or a Bookshelf staff member can use an admin interface to update an image on the publisher's behalf.

In our bookshelf app, an `Admin` supports the latter functionality, and a `Publisher` slice the former. Both these slices want to trigger a CDN purge when a book cover is updated, but neither slice necessarily needs to know how that's acheived. Instead, a `CDN` slice can manage this operation.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
In our bookshelf app, an `Admin` supports the latter functionality, and a `Publisher` slice the former. Both these slices want to trigger a CDN purge when a book cover is updated, but neither slice necessarily needs to know how that's acheived. Instead, a `CDN` slice can manage this operation.
In our bookshelf app, an `Admin` slice supports the latter functionality, and a `Publisher` slice the former. Both these slices want to trigger a CDN purge when a book cover is updated, but neither slice necessarily needs to know how that's acheived. Instead, a `CDN` slice can manage this operation.


Any slice can be optionally configured by creating a file at `config/slices/slice_name.rb`.

Here, we configure the CDN slice to export is purge component:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we skip here is that slices will by default export all of their components. Do you feel that is worth a mention as part of this progression? That way we'd go from loosest->strictest in full.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, yep. Adjusted this to put the export function last.

```


# Slice imports and exports
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're missing a section above this one describing the import of core components from the app, i.e. everything in the app's config.shared_app_component_keys, which by default is:

%w[
  inflector
  logger
  notifications
  rack.monitor
  routes
  settings
]

A couple of those components are internal, but I think it would be useful to share how slices automatically share the same inflector, logger, settings, and routes helper as the application, ensuring consistent core behaviour even as the app is decomposed into distinct slices.

In fact, this might also hint at another section, likely most appropriate for the bottom end of this doc, about the role of the app-level directories (app, providers, config) for an app that fully embraces slices.

Comment on lines +49 to +50
- can be prepared and booted independently of other slices
- can have its own slice-specific settings (e.g. `slices/api/config/settings.rb`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like these two warrant their own sections, however brief.

Copy link
Member

@timriley timriley left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short review for routing.md 😆 Looking forward to reading the rest!

## Composing a route

In the Hanami router, each route is comprised of:
- a HTTP method (i.e. `get`, `post`, `put`, `patch`, `delete`, `options` or `trace`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- a HTTP method (i.e. `get`, `post`, `put`, `patch`, `delete`, `options` or `trace`)
- an HTTP method (i.e. `get`, `post`, `put`, `patch`, `delete`, `options` or `trace`)

?

- a path
- an endpoint to be invoked.

Endpoints are usually actions within your application, but they can also be a block, a rack application, or anything that responds to `#call`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Endpoints are usually actions within your application, but they can also be a block, a rack application, or anything that responds to `#call`.
Endpoints are usually actions within your application, but they can also be a block, a Rack application, or anything that responds to `#call`.

Proper Nouned Rack here. I wonder if we also want to link it to https://github.com/rack/rack?

andrewcroome added a commit to hanami/guides that referenced this pull request Nov 8, 2022
These are a combination of existing 1.3 materials and new work, including by @swilgosz

Relevant source PRs:

#99
#101
hanami/guides-v2#1
andrewcroome added a commit to hanami/guides that referenced this pull request Nov 8, 2022
These are a combination of existing 1.3 materials and new work, including by @swilgosz

Relevant source PRs:

#99
#101
hanami/guides-v2#1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants