You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/application-architecture/slices.md
+249-6Lines changed: 249 additions & 6 deletions
Original file line number
Diff line number
Diff line change
@@ -4,17 +4,21 @@ sidebar_position: 3
4
4
5
5
# Slices
6
6
7
-
In addition to the `/app` directory, Hanami also supports organising your application code into **slices**.
7
+
In addition to the `app` directory, Hanami also supports organising your application code into **slices**.
8
8
9
-
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 by feature concern (api or search).
9
+
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).
10
10
11
-
Slices live in the `/slices` directory.
11
+
Slices exist in the `slices` directory.
12
+
## Creating a slice
12
13
13
-
To create a slice, you can either create a new directory in `/slices`:
14
+
To create a slice, you can either create a new directory in `slices`:
14
15
15
-
```ruby
16
+
```shell
16
17
mkdir -p slices/admin
17
18
19
+
slices
20
+
└── admin
21
+
18
22
bundle exec hanami console
19
23
Admin::Slice
20
24
=> Admin::Slice
@@ -26,10 +30,249 @@ Or run `bundle exec hanami generate slice api`, which has the added benefit of a
26
30
bundle exec hanami generate slice api
27
31
28
32
slices
33
+
├── admin
29
34
└── api
30
35
├── action.rb
31
36
└── actions
32
37
```
33
38
39
+
## Features of a slice
40
+
41
+
Slices offer much of the same behaviour and features as Hanami's `app` folder.
42
+
43
+
A Hanami slice:
44
+
45
+
- has its own container (e.g. `API::Slice.container`)
46
+
- can have its own providers (e.g. `slices/api/providers/my_provider.rb`)
47
+
- can include actions, routable from the application's router
48
+
- can import and export components from other slices
49
+
- can be prepared and booted independently of other slices
50
+
- can have its own slice-specific settings (e.g. `slices/api/config/settings.rb`)
51
+
52
+
## Slice containers
53
+
54
+
Like Hanami's `app` folder, components added to a Hanami slice are automatically organised into the slice's container.
55
+
56
+
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:
halt 422, {error:"Unprocessable country code"}.to_json unless request.params.valid?
79
+
80
+
result = query.call(
81
+
request.params[:country_code]
82
+
)
83
+
84
+
response.body = result.to_json
85
+
end
86
+
end
87
+
end
88
+
end
89
+
end
90
+
```
91
+
92
+
This action checks that the provided country code (`request.params[:country_code]`) is a valid ISO3166 code (using the countries gem) and returns a 422 response if it isn't.
93
+
94
+
If the code is valid, the action calls the countries show query (by including the `"queries.countries.show"` item from the slice's container - aliased here as `query` for readability). That class might look like:
To add a route for our action, we can add the below to our application's `config/routes.rb` file. This is done for you if you used the action generator.
146
+
147
+
```ruby title="config/routes.rb"
148
+
# frozen_string_literal: true
149
+
150
+
moduleBookshelf
151
+
classRoutes < Hanami::Routes
152
+
root { "Hello from Hanami" }
153
+
154
+
slice :api, at:"/api"do
155
+
get "/countries/:country_code", to:"countries.show"
156
+
end
157
+
end
158
+
end
159
+
```
160
+
161
+
`slice :api` tells the router it can find actions for the routes within the block in the API slice. `at: "/api"` specifies an optional mount point, such the routes in the block each be mounted at `/api`.
162
+
163
+
After running `bundle exec hanami server`, the endpoint can be hit via a `GET` request to `/api/countries/:country_code`:
164
+
165
+
```shell
166
+
curl http://localhost:2300/api/countries/AU
167
+
{"name":"Australia","flag":"🇦🇺","currency":"AUD"}
168
+
```
169
+
170
+
171
+
# Slice imports and exports
172
+
173
+
Suppose that our bookshelf application uses a content delivery network (CDN) to serve book covers. While this makes these images fast to download, it does mean that book covers need to be purged from the CDN when they change, in order for freshly updated images to take their place.
174
+
175
+
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.
176
+
177
+
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.
178
+
179
+
```ruby title="slices/cdn/book_covers/purge.rb"
180
+
moduleCDN
181
+
moduleBookCovers
182
+
classPurge
183
+
defcall(book_cover_path)
184
+
puts"Purging #{book_cover_path}"
185
+
# "Purging logic here!"
186
+
end
187
+
end
188
+
end
189
+
end
190
+
```
191
+
192
+
To allow slices other than the CDN slice to use this component, we first export it from the CDN.
193
+
194
+
Any slice can be optionally configured by creating a file at `config/slices/slice_name.rb`.
195
+
196
+
Here, we configure the CDN slice to export is purge component:
197
+
198
+
```ruby title="config/slices/cdn.rb"
199
+
moduleCDN
200
+
classSlice < Hanami::Slice
201
+
export ["book_covers.purge"]
202
+
end
203
+
end
204
+
```
205
+
206
+
Now, the `Admin` slice can be configured to import _everything_ that the CDN slice exports:
# ... update the book using the book repository ...
245
+
246
+
# If the update is successful, purge the book cover from the CDN
247
+
purge.call(book.cover_path)
248
+
end
249
+
end
250
+
end
251
+
end
252
+
end
253
+
```
254
+
255
+
It's also possible to import only specific exports from another slice. Here for example, the `Publisher` slice imports strictly the purge operation, while also - for reasons of its own choosing - using the suffix `content_network` instead of `cdn`:
0 commit comments