Open nelsonic opened 5 years ago
The reason I think that this example is highly relevant and useful both to us (@dwyl) and to anyone else wanting to understand both the "Why?" and "How?" of append-only (accountable) application architecture is that having a simple example without any dependencies makes it clear how everything works.
I'm going to make a stab at the todo list above. I will log my progress as I go and create a PR once there is something to review.
Edit: To be clear: the reason I think this is worth doing first is to understand the "alog" problem from "first principals"
before
attempting to generalise the solution in github.com/dwyl/alog Hopefully others will follow my logic here. 🌈
Going to try and use mix phx.gen.html
command to generate the HTML (form) for the address book:
https://hexdocs.pm/phoenix/Mix.Tasks.Phx.Gen.Html.html
Take the mix phx.gen.schema Address
command as the basis and adapt it for gen.html
mix phx.gen.schema Address addresses name:string address_line_1:string address_line_2:string city:string postcode:string tel:string
Gen HTML:
mix phx.gen.html Address addresses name:string address_line_1:string address_line_2:string city:string postcode:string tel:string
Got the following error:
** (Mix) Expected the schema, "addresses", to be a valid module name
mix phx.gen.html, phx.gen.json and phx.gen.context expect a
context module name, followed by singular and plural names of
the generated resource, ending with any number of attributes.
For example:
mix phx.gen.html Accounts User users name:string
mix phx.gen.json Accounts User users name:string
mix phx.gen.context Accounts User users name:string
The context serves as the API boundary for the given resource.
Multiple resources may belong to a context and a resource may be
split over distinct contexts (such as Accounts.User and Payments.User).
In my own "throw away" example: https://github.com/nelsonic/append-only-log-ex
I'm going to try deleting the schema and re-creating it using the gen.html
command:
mix ecto.drop && MIX_ENV=test mix ecto.drop
You should expect to see:
The database for Append.Repo has been dropped
The database for Append.Repo has been dropped
Try running the Gen HTML command again:
mix phx.gen.html Address addresses name:string address_line_1:string address_line_2:string city:string postcode:string tel:string
That failed because the address.ex file already exists.
Deleting the file: /append-only-log-ex/lib/append/address.ex
Also deleting the migrations:
After deleting the files, let's attempt to run the gen.html
command again:
mix phx.gen.html Address addresses name:string address_line_1:string address_line_2:string city:string postcode:string tel:string
Derp, we need to add the context! Error message:
** (Mix) Expected the schema, "addresses", to be a valid module name
mix phx.gen.html, phx.gen.json and phx.gen.context expect a
context module name, followed by singular and plural names of
the generated resource, ending with any number of attributes.
For example:
mix phx.gen.html Accounts User users name:string
mix phx.gen.json Accounts User users name:string
mix phx.gen.context Accounts User users name:string
The context serves as the API boundary for the given resource.
Multiple resources may belong to a context and a resource may be
split over distinct contexts (such as Accounts.User and Payments.User).
Add the "Accounts" context to the the gen.html
command and run it again:
mix phx.gen.html Accounts Address addresses name:string address_line_1:string address_line_2:string city:string postcode:string tel:string
This time it worked: 🎉
* creating lib/append_web/controllers/address_controller.ex
* creating lib/append_web/templates/address/edit.html.eex
* creating lib/append_web/templates/address/form.html.eex
* creating lib/append_web/templates/address/index.html.eex
* creating lib/append_web/templates/address/new.html.eex
* creating lib/append_web/templates/address/show.html.eex
* creating lib/append_web/views/address_view.ex
* creating test/append_web/controllers/address_controller_test.exs
* creating lib/append/accounts/address.ex
* creating priv/repo/migrations/20190425093946_create_addresses.exs
* creating lib/append/accounts.ex
* injecting lib/append/accounts.ex
* creating test/append/accounts/accounts_test.exs
* injecting test/append/accounts/accounts_test.exs
Add the resource to your browser scope in lib/append_web/router.ex:
resources "/addresses", AddressController
Remember to update your repository by running migrations:
$ mix ecto.migrate
Going to follow the instructions given after running the gen.html
command:
open the lib/append_web/router.ex
file and add
resources "/addresses", AddressController
Update your repository by running migrations:
$ mix ecto.migrate
Fails:
[error] Postgrex.Protocol (#PID<0.301.0>) failed to connect: ** (Postgrex.Error) FATAL 3D000 (invalid_catalog_name) database "append_dev" does not exist
[error] Postgrex.Protocol (#PID<0.300.0>) failed to connect: ** (Postgrex.Error) FATAL 3D000 (invalid_catalog_name) database "append_dev" does not exist
[error] Postgrex.Protocol (#PID<0.301.0>) failed to connect: ** (Postgrex.Error) FATAL 3D000 (invalid_catalog_name) database "append_dev" does not exist
[error] Could not create schema migrations table. This error usually happens due to the following:
* The database does not exist
* The "schema_migrations" table, which Ecto uses for managing
migrations, was defined by another library
To fix the first issue, run "mix ecto.create".
To address the second, you can run "mix ecto.drop" followed by
"mix ecto.create". Alternatively you may configure Ecto to use
another table for managing migrations:
config :append, Append.Repo,
migration_source: "some_other_table_for_schema_migrations"
The full error report is shown below.
Run the drop command:
mix ecto.drop && MIX_ENV=test mix ecto.drop
Then run the migrate command again:
mix ecto.create
mix ecto.migrate
Output: (success)
[info] == Running 20190425093946 Append.Repo.Migrations.CreateAddresses.change/0 forward
[info] create table addresses
[info] == Migrated 20190425093946 in 0.0s
Run the mix phx.server command to start the server:
mix phx.server
Visit the app in the browser: http://localhost:4000/addresses
With the addition of the Accounts
context the address.ex
schema is now in: /lib/append/accounts/address.ex
so that's where we need to make our timestamp change in step 2 of the tutorial.
Now we can run the migrate:
mix ecto.migrate
Output:
Compiling 1 file (.ex)
[info] == Running 20190425093946 Append.Repo.Migrations.CreateAddresses.change/0 forward
[info] create table addresses
[info] execute "REVOKE UPDATE, DELETE ON TABLE addresses FROM append_only"
[info] == Migrated 20190425093946 in 0.0s
Run the app mix phx.server
Visit http://localhost:4000/addresses/new
Totes works!
Now attempt to edit the address: http://localhost:4000/addresses/1/edit
Fails because we revoked the UPDATE privileges in our migration (above):
The question we have to ask ourselves now is: are we going to use the default phoenix auto-incrementing id (in the above case 1
) for the id for records. That will always result in errors.
in the /test/append/address_test.exs
we need to change the line:
alias Append.Address
To:
alias Append.Accounts.Address
No changes were required to the lib/append/append_only_log.ex
section of the existing tutorial
we just had to open the file /lib/append/accounts/address.ex
add the line:
use Append.AppendOnlyLog #include the functions from this module's '__using__' macro.
First test passes:
In the tutorial, remember to change all instances of Append.Address
to Append.Accounts.Address
Remember to link to the issue discussing merits of macros: https://github.com/dwyl/phoenix-ecto-append-only-log-example/issues/8
Minor detour (SPIKE) to investigate storing history of a record: https://github.com/dwyl/ecto-postgres-pubsub-spike/issues/1
Reading the example/tutorial code is nice, 👍 but I feel that having an example app on Heroku would be much more beginner-friendly. 🤔 Who wants to make this happen? :-)
Example: https://github.com/dwyl/phoenix-chat-example
Todo