A newbie's journey with Hanami


#1

Hey everyone, thought I’d make an introduction, and can certainly use your guidance and suggestions :slight_smile:

We (another jr dev and I) are rebuilding a node-express, jquery, firebase prototype with pg, hanami, jwt, react. We picked Hanami over Rails, Express.js, and Spring.jar because it seems very thoughtfully built, small in size, have good documentation (wow TDD docs!).

I’ve worked mostly with JS in production, have done some plain old Ruby, and love the language - but definitely lack knowledge in web configuration, the ecosystem, and the Ruby way :gem:

So far we’ve been referencing OSSBoard and the official tutorial to get started.
Some config info:

  • .erb until we set up an api container to talk to React
  • omniauth to handle OAuth
  • JWT to auth api (will use this built-with-hanami for reference)

Currently trying to get omniauth working, then add in a jwt service.
Next is to figure out the repository, see about building associations and aggregates.

Question
Since we plan to use JWT, is sessions still required? Omniauth doesn’t seem to work without use Rack::Session::Cookie and a secret is generated for each container - can those be ignored for now?


#2

@billiam Hi and welcome.

For OmniAuth, yes, you need a Rack session storage. Cookies are fine for the purpose.

I don’t have practical experience with JWT, but I’ve seen people over and over on social networks to suggest to not using JWT as session storage because of the security implications. Eg. http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/


#3

Hello, @billiam! :clap:
I can help with omniauth. I created the little application for my hanami workshop. You can play with it, and also, you can see that I set omniauth as a middleware. After this changes, you can set session only in config.ru file and all will works correct :slight_smile:


#4

Awesome, thanks for the resource @davydovanton! Will take a look.

@jodosha it’s definitely a valid security risk if JWT is stored in localStorage, on one of my other projects we used the token to also pass some information to be decoded and used on the client side, and it’s also shared with a browser extension exactly because JS can access it. How about encrypting the token on the client side? My own client scripts can get the token and decrypt it, other scripts can access but not decrypt.

In our use case we do use stateless tokens to share access, and not having to couple server scaling to a sessions table or sticky sessions makes it easier to manage deployment and auto-scaling containers


#5

Do you guys have suggestions for making a many-to-many relationship?
I found this method here that suggests no, what’s the design decision behind that?

  def self.lookup(association)
    case association
    when ROM::SQL::Association::OneToMany
      Associations::HasMany
    when ROM::SQL::Association::ManyToOne
      Associations::BelongsTo
    else
      raise "Unsupported association: #{association}"
    end
  end

Say for Teacher has many Students through Classes. I vaguely remember something about building aggregates, that access to its children is through the aggregate root, so, nothing like a_teacher.classes.first.students.first.teachers. But haven’t figured out why yet (searching around on aggregates and many-to-many right now).

If a Class is the aggregate root, a class has many Teachers and many Students, and a lot of duplication with another Class. When I want to see which class Teacher Tanya teaches, I’d have to iterate through all Classes for occurrences of Tanya. But in many-to-many, I’d be able to find Tanya and query Teacher#classes.


#6

In http://hanamirb.org/guides/models/entities/
There’s an example of entity User has Comments.

How can User be persisted together with the Comments?
Idea: store User and Comment as whole objects
Implementation: PG would need a composite type for User’s column, something like User{ id: uuid-123, name: 'Anders', comments: [ Comment{id: uuid-987, text: 'hello'}, Comment{id: uuid-876, text: 'world'} ] }
Issue: an Article has many comments as well, how to share the same comments to Article? When a user deletes a comment, how to reflect that in the Article?

How to share the comments to Article?
Idea: User holds onto a collection of Comment by UUID, Article also holds onto a collection of Comment by UUID.
Implementation: User{ comment_ids: ['uuid-987', 'uuid-876']}, Article{ comment_ids: ['uuid-987', 'uuid-876']
Issue: when user deletes a comment, article doesn’t know, and it will be pointing to nil

idea: either an application service orchestrating the User and Article’s comments, or a pubsub so entities react to events. Looking into the two options now


#7

@billiam The fact that we didn’t added all the expected associations is due to the fact that has_many has an experimental API. Indeed, we’re already working to improve it and extend to all the other types of associations. /cc @mereghost


#8

Entities != persistence.

That example shows how you can define an entity that is not backed by a database schema. When using a SQL database, each table structure is read and the attributes for that entity are set automatically.

Alongside with that, if an “user has many comments”, Hanami adds another attribute which is made of a collection (Array) of Comment. But here we are in Ruby land. The entity doesn’t know that there is a database somewhere.

Thanks to the associations, a repository knows how to persist that data structure in two tables: users and comments.

In other words, comments are NOT embedded inside an user record, but they are in a one-to-many relationship.

Again, that “manual schema” is useful to define attributes when you have a schemaless database.[quote=“billiam, post:6, topic:317”]
How to share the comments to Article?
[/quote]

I suspect that this question is related to a misinterpretation of the Entities guide. Is it correct?