taro

taro

I took lessons from the last discussion and cobbled together an example as a proof-of-concept.

Mar demonstrates a Flask-like web dev interface powered by Plug on Bandit.
use Mar immediately makes a module a route. The user modules don’t need to report to an entry-point plug in the app. Library handles routing.
Normal defs defines the actions to the requests. So you can compose them Elixir way. Router matches them by their names and the HTTP methods.

defmodule MyApp do
  use Mar

  def get(), do: "Hello, world!"
end

path can be set. Default is "/" otherwise.
params declares allowed parameters alongside path parameters with :. Later it takes matching keys from conn.params and puts it in the actions.
Actions can return a string, a map for JSON, or a tuple being {status, headers, body}.

defmodule MyApp do
  use Mar, path: "/post/:id", params: [:comment]

  def get(%{id: id}) do
    "You are reading #{id}"
  end

  def post(%{id: id, comment: comment}) do
    %{
      id: id,
      comment: comment
    }
  end

  def delete(%{id: _id}) do
    {301, [location: "/"], nil}
  end
end

Routes can interact with the library through Mar.Route protocol.

defmodule MyApp do
  use Mar

  def get(), do: "Hello, world!"

  defimpl Mar.Route do
    # Mar.Route.MyApp
    def before_action(route) do
      IO.inspect(route.conn.resp_body)
      # => nil
      route
    end

    def after_action(route) do
      IO.inspect(route.conn.resp_body)
      # => "Hello, world!"
      route
    end
  end
end

Intention

While there are many possible approaches to helping adoption and facilitating learning, the challenge I tackle here is to nicely encapsulate Plug and reduce cognitive load for the users. @taro lacks the technical capability for something production-ready, this project waxes on top of Bandit with an escape hatch in an attempt to help you envision a light and intuitive web framework for Elixir.

The insight and guidance from the community is much appreciated:

Design

This library relies on protocol consolidation to handle routes. use Mar injects a default defimpl of Mar.Route protocol. It lists up the user modules. At the same time, defstruct saves the path as a default struct value. Then the list of implementation maps to structs, which has information for path-matching.

case Mar.Route.__protocol__(:impls) do
  {:consolidated, modules} -> Enum.map(modules, &struct(&1))
  :not_consolidated -> []
end
# [ 
#  %MyApp{ path: "/", ...}, 
#  %MyApp.Post{ path: "/post/:id/", ...}, ,
#  ...
# ]

Mar.Router leaves escape hatches open with the Mar.Route protocol. The user modules redefine the functions with defimpl to access them.

# Mar.Router
def call(conn, _options) do
  # Match routes, load params
  route = Mar.Route.before_action(route)
  # Apply action
  route = Mar.Route.after_action(route)
  # Send response
end

Atom keys are preferred over string keys for the sake of nicer syntax. That’s also why params need to be declared so the library can prevent dynamic atom creation.

What do you think? I’m hoping to hear from you! :smile:

Reference

Showing Posts 1 to 10

te_chris

te_chris

You got dragged a bit in the other thread but I think this really cool and a worthwhile addition to the library ecosystem

taro

taro OP

Thanks for the words of encouragement!
I’m looking forward to improve upon current version and expand the project further, including zero-to-hero guides and templating.

josevalim

josevalim

Creator of Elixir

Hi @taro!

Thank you for exploring new directions here. If your goal is to have something smaller, may I suggest something that builds on top of functions rather than modules? Modules impose more boilerplate than functions and relying on protocol consolidation means you can’t use Mar efficiently inside Mix.install/2 scripts (you have to disable consolidation or use a full-blown project).

Compare with the simplest hello world possible with Bandit:

Mix.install([:bandit])

Bandit.start_link(plug: fn conn, _opts ->
  Plug.Conn.send_resp(conn, 200, "hello world")
end)

There is probably a balance to be found between functions and modules here.

19
Post #3
taro

taro OP

Hi @josevalim !
Thanks for your advice. It means a lot to me.

The goal is to make it ergonomic, and not the least LoC even though I’ve got the single .exss as the responses in the other thread.

What I’m trying is to maximise the semantics of Elixir in web development. Modules are a meaningful units in Elixir, usually saved in its own file. So I wanted to put some meaning to it. A path usually has dozens of actions. That’s how I came up with module-path, function-action analogy.

Do you think this approach has a chance if the least LoC is not the goal?

That was exactly what I wanted to solve next :sob: Is it hopeless then? I couldn’t find more information about how consolidation works under the hood and why Mix.install/2 breaks it.

I want to remove the need to reporting modules to the configurations of a library or a centralised router. Is this an anti-pattern or unnatural thing in Elixir? I see many people have tried this, ending up scanning all the modules and saving them in ETS or an Agent.

dimitarvp

dimitarvp

I am most likely not paying attention well enough here but why did you go for protocols and not behaviours? Or even functions tagged with attributes?

taro

taro OP

Hi @dimitarvp !

Mar doesn’t have much to do with behaviour yet. I don’t know where to use them? I’m trying hard to remove one more thing that the user needs to do. Behaviour seems a lot of do this do that for the user. Tagging functions seems not much different. I’d appreciate it if you let me know what I can do!

Protocol is for inter-project communication. It can

  1. Register the module to the Mar.Route.
  2. Save information in the %MyApp{} struct.
  3. Interact with the library through Mar.Route.MyApp

Now, it removes and hides less important things. User modules don’t need to report themselves to a configuration because protocol know where they are. The library code can handle the routes polymorphically. User module can access them if they need to.

Or, I’m not seeing what your questions imply.
Is there an obviously better approach?

dimitarvp

dimitarvp

As I said I am not even sure my question is good, it was borne out of how much I hate the defimpl blocks, they stick like sore thumbs for me. :smiley:

jerdew

jerdew

I ran across this myself when trying something in livebook. It’s a quick add to the install line: Mix.install([:mar], consolidate_protocols: false)

This post may be helpful: Why does Livebook require `consolidate_protocols: false`

taro

taro OP

I see, could you briefly share what’s wrong with them?
In Mar, I intended them to be optional escape hatches when the user module want the library to handle the route differently. You have direct access to conn in it.

taro

taro OP

Hi @jerdew
Thanks for the info!

The problem runs deeper than adding impls for this library though. It uses the list of consolidated modules as a registry. So it needs consolidation anyway. I thought I would find a way to reconsolidate or something. Or I have to make another way to keep modules with use Mar in one place.

Where Next? Top

Trending in Announcing Top

bluzky
You may know https://ui.shadcn.com/, a UI component library for React. I really love it’s design style and components. I’ve built some co...
387 15136 120
New
wojtekmach
Hey everyone! Req is an HTTP client for Elixir that I’ve been working on for quite some time. There is already a lot of HTTP clients out...
New
handnot2
Samly can be used to enable SAML 2.0 Single Sign On in a Plug/Phoenix application. This library uses Erlang esaml to provide plug enabl...
New
woylie
Flop is an Elixir library that applies filtering, ordering and pagination parameters to your Ecto queries. offset-based pagination with...
New
restlessronin
The repo is at GitHub - cyberchitta/openai_ex: Community maintained Elixir library for OpenAI API · GitHub. Docs are at OpenaiEx User Gu...
152 11030 135
New
shahryarjb
The Chelekom project is a library of Phoenix and LiveView components generated via Mix tasks to fit developer needs seamlessly. One of i...
New
garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
New

Other Trending Topics Top

mudasobwa
I am seeing a lot of aplications of Argumentum ad Vericundiam in software discussions. They do link some piece of writing and point us to...
New
bartblast
Hey folks, I just published a post about Hologram’s funding and where the project goes next - the short version: Curiosum as Main Spons...
New
alexslade
Fly’s CEO posted this recently - Turn And Face The Strange · The Fly Blog It says that Fly is going all-in on sprites, which is a worry ...
New
Herve37
We’re evaluating API mocking tools for OpenAPI-based projects and would love to hear what other teams are using. We’re particularly inte...
New
sorenone
Today we’re releasing Oban for Python. Not an Oban client in Python. Not a pythonx wrapper embedded in Elixir. Nope, it’s a fully operati...
New
lawik
I was thinking since Goatmire Elixir turned out pretty good I should maybe do another one. 30th of Sep - 2nd of Oct this year./ The firs...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews