How do you develop complete projects with Phoenix?

I recently finished the Udemy course on Elixir and Phoenix and I am thinking about using it for the next project. But I am stuck as how to start developing, what are the steps I should take, should I follow a top-down or bottom-up approach to developing e.t.c.

When I was starting with react, this guide gave me a lot of great pointers on the development philosophy of react. Does any such guide or talks that exist for Phoenix ?

I am not looking for specific technical questions but general good practices like

  1. Should I build the entire mockup in HTML with placeholder data then implement the backend logic to fill in ?
  2. Should I use generators everytime or just one time and then fill in everything manually ?
  3. Should I be testing everything or upto what depth ?

It would also be nice if you could elaborate your workflow, how you tackle a new Phoenix project how you lay everything out, how you design your databases e.t.c

Any help is greatly appreciated, Thanks :smiley:

1 Like

Write one to throw away.

pen and paper…

UI mockups
DB/schemas

1 Like

There are 2 approaches I now adopt, sometimes I will do them concurrently when I am bored with one of them.

The first approach is start with the UI. Do some sketches of your UI. If you feel like it, use some UI tools (figma is my personal favourite) for higher fidelity mockups. Then code the UI (without the backend logic) using your frontend framework, be it React or Phoenix HTML. Finally, I will think of the API endpoint that my UI might need, and conclude it with mock data.

This process is iterative. So it doesn’t mean you will do up all the UI screens before you start defining your APIs.

The second approach is inside out. I will start off with the core of the application. The core of the application is not the DB or data persistence layer. In fact, I make it a point not to do any DB migrations until I finish coding the core business logic. I rely on TDD to tell me whether is my core business logic correct.

To me, the core business logic is what differentiates your application from the rest. It is something that will remain the same no matter what medium or technology you are using. For example, if I am writing an accounting application, my core business logic is generating the financial statements. The logic in generating financial statements remain the same no matter you are using pen and paper, or you are using Excel spreadsheet. I will first write the code that takes in some input, and then generate the data required for the financial statements.

With this done, then I will write the DB layer, and finally the API layer.

The second approach is the inside out approach.

3 Likes

Thanks for the informative guide.

Regarding the first approach, do you just type out the mock data from inside the controllers ?

Also do you use generators or type everything by hand ?

I guess there are no completely objective answers to this question, so here goes my opinionated - uh… - opinion.

I usually look at the problem at hand and create a data model - it fits my way of thinking about problems, but it’s maybe not how other people would start out. This is really framework agnostic, but since web development is just creating nice database interfaces, I like to tackle that first.

Phoenix contexts are (afaik) based on domain driven design, and I’m sure there will be people who think what I’m doing is nuts, and I’m doing it totally wrong, so take everything with a grain of salt, but I think DDD is the way to go in Phoenix.

Ninigi production proudly presents

A **you sure you know what you’re doing bro?** guide to phoenixing

featuring Me

introducing Me

Co Star Mila Kunis (just kidding - but she’d be welcome)

My take on DDD in Phoenix

Legend fades to myth, and even myth is long forgotten when the Age that gave it birth comes again.
In one Age, called the Third Age by some, an Age yet to come, an Age long past, a wind rose above the self-righteous people of Programmers.
The wind was not the beginning.
There are neither beginnings nor endings to the Wheel of Time.
But it was a beginning.

****

Act One: How Star Wars The Rise of Skywalker tried to be a Steven Spielberg adventure movie
or what to do with Phoenix contexts

Since I like to start with the data model, I try to fit those into features, like for example when you are building a web-shop admin interface, there might be Stocking, Shipping, ProductManagement, OrderManagement, … - note how those contexts refer to “interactions”, rather than something generic like Products. You can add those generic contexts if you have a big project and want to encapsulate your entities from the things you do with them, but in smaller projects I feel like it’s total overkill.

Phoenix provides very neat generators for pretty much everything, but I don’t use them anymore (it was a nice learning tool though), because I usually end up editing everything and deleting unused functions anyways (it’s much easier to just write a database query than to use some getter function that is never used in the code base). So my next step is usually to create a context (like ProductManagement), migrations, and schemas. What you can take from those generated files though is the wording for functions

def list_something
def get_something
def create_something
def update_something
def delete_something

CRUD functions are usually a very nice thing to have, and for many apps it’s all you really need, but if you need to introduce more specialized things, I think it is just better to stick to this convention.
Here are some things I have seen in phoenix projects and wouldn’t recommend (not because it doesn’t work, it just goes against the convention the phoenix generators introduce)

# mixing get with list
def get_products

# acting like your function is a method or an attribute
def products

# changing the expected return value to something "someone once wanted in a view - uh... no idea what... uh"
def list_products do
  Product
  |> Repo.all()
  |> Enum.map(fn product ->
    Map.put(product, :something_not_in_the_schema, good_luck_following_this(product))
  end)
  |> transform_everything_so_its_not_a_product_anymore()
end

# returning something completely different from the arguments (unless it is explicit in the function name)
# and one more tip: don't call your schema "Package" in any language, ever (hence `Parcel`)
def fulfill_parcel(%Parcel{} = parcel) do
  parcel =
    parcel
    |> update_parcel(shipped: true)
  fulfill_items(parcel.items)
  # you see the problem here? does this return items, or the updated parcel?
  # in any case, one of the functions is misnamed
end

I also strongly dislike a feature context sporting functions to get or list everything. Your features usually have one or two (maybe three) core entities. When dealing with stock, there could be an Item, a ShelfLocation, a Manufacturer, multiple PurchaseSources, a User would be stocking it, maybe the item arrived just in time and you want to send it out right away so there would be an Order, and a Parcel waiting for that item. If your feature contexts provide all those getter functions, you are watering down the intention of those contexts and will end up with a lot of duplications.
A good indicator to when you might want to introduce entity specific contexts in your app is when you are starting to write the same thing with different return values in multiple contexts

iex> Stocking.get_product(213)
%ProductManagement.Product{items: [%Stocking.Item{}]}

iex> ProductManagement.get_product(213)
%ProductManagement.Product{items: #Ecto.NotLoadedWhatever}

# Feels weird right? Solutions:

# Option 1 generic entity contexts

iex> Stocking.get_product(213)
%Products.Product{items: [%Stocking.Item{}]}

iex> ProductManagement.get_product(213)
%Products.Product{items: #Ecto.NotLoadedWhatever}

# Option 2 feature context specific schemas

iex> Stocking.get_product(213)
%Stocking.Product{items: [%Stocking.Item{}]}

iex> ProductManagement.get_product(213)
%ProductManagement.Product{items: #Ecto.NotLoadedWhatever}

We have actually experimented with that in our company, and my verdict is:

  • For “1 - 3 humans” sized projects, Option 1 is a nice way to handle complexity in bigger projects, and makes the code easier to parse (for humans)
  • In big projects with big companies, and big budgets, and a lot of programmers messing with the code, and a lot of “ands” I can’t even imagine, Option 2 is probably superior, but it means a lot more work for smaller scale projects (not worth it)

When going with Option 1, your feature contexts only contain business logic, using schemas from the entity contexts (and the more atomic CRUD functions in those), and also make room for some feature specific entities (in elixir a struct is usually what you want.) And that brings me to how I like to build my views. Not the Phoenix View as a concept in the framework, but in the more literal way as in “how do I display my database tables so my grandma would not get confused.” (ok the grandma bit is a little over the top… no one can do that…)

Note: I think this concept even has a name, something like onion DDD? Layered DDD?

****

Act Two: How Juliet was a 13 year old girl, who couldn’t handle her boyfriend of a few days “dying”
or how to get started with visual stuff in Phoenix

When dealing with a framework like phoenix, I like to start my user faced stuff by writing HTML templates, roughly outlining what it needs to do, no custom CSS yet outside of some basic framework grids (Bootstrap, Carbon, Foundation, Materialize, Skeleton - pick your poison), no javascript, just the basic HTML. I do that even if I have very detailed mockups.
Then I write my embedded elixir the way I would like to use it, and deduct the functions I need for my view step by step - for example:

  • I need to display all parcels ready to ship, so a worker can go through them and pack the items
  • write something like for parcel <- @parcels do
  • go back to the controller, write how I would like to load parcels in this case: Shipping.list_ready_to_ship_parcels()
  • :exclamation: write the @doc for Shipping.list_ready_to_ship_parcels() first. Seriously, this should be something pinned on all projects, I’d even vote for a compile time error for undocumented functions! I don’t know how many times I started writing docs first and realized that this is not what I actually need, or that the naming was off, or that it was not returning what it should, or that it had too many side effects not reflected in the function name etc. Or sometimes I wrote the docs after implementing and realized it doesn’t make sense. A function doc is your rubber duck, use it. :exclamation:
  • write a test for the top level function test "loads only parcels that are ready to ship" - write the test before implementing, to make sure you know what you want it to do (this also helps with writing factories, and ultimately it should make you think about how states advance from 0 to X - a test factory should not be hard to set up, and if it is, you need a function to advance you, and if you need to advance, you need to test that in an integration test)
  • implement list_ready_to_ship_parcels without preloading any associations yet (of course at this point, there would be a lot more already implemented, you would have to start at “how do users even buy items for stocking, and where do the orders come from”)
  • If you worry about performance, add opts // [] to the list function - useful options are things like page, per_page, cursor, filter (for simple where clauses, or maybe you want to change the entire query if a certain filter is given), maybe preloads…
  • Add tests for the options (in that order - the tests just make sure that nobody accidentally changes a query behavior)
  • Reload the page and debug (add edge cases to tests as they come up, it is going to save you headaches later)

Answering your question: Phoenix is a great framework to work top down with a bottom already built, if that makes sense.
I don’t like pure HTML/CSS/Javascript mocks to get a project started, I don’t think it makes sense to have the styling down to the last pixel, if you might want to introduce a different way of handling interactions.

****

Interlude: How Rainhold totally left Günther to die, but it wasn’t Rainhold’s fault
or Ninigi rants about something nobody cares about - if you do, get a life

Mockups are great tools to get an idea of what the Product Owner wants - the word Product Owner has gained a lot of negative connotations since I first heard of it, but I actually mean it in a positive way: you are building something, someone had an idea of what that something is (maybe it was you, maybe not) and someone wants to build it (again, maybe not you).
A Product Owner for me is someone who claims the product, the development process, the marketing, and ownes up to everything related to the app. The person coming up with an idea is not necessarily the product owner, the person building it isn’t, and neither is the person ultimately making money from it - it’s the person who takes care of all those things happening, coordinating them and… I just realized this does not belong here - but I don’t want to delete it.

Act three: How Ross and Rachel totally were on a break - and both of them still are bad people
or how to utilize Phoenix to the best

I am not gonna lie, coming from most other frameworks, Phoenix is kind of different, but not really - which makes it easy to pick up, but hard to utilize the true power of the Phoenix (sorry, couldn’t help myself - the framework name needs some pathos!)
Phoenix shines when it comes to client-server push-pull interactions. Most projects don’t attract the amount of users that would make a - lets say - Ruby on Rails app collapse. If you do expect that amount of users: good for you! But you would still only scratch the surface of what a Phoenix can do.

IMO, the best use-cases for phoenix right now are heavy client-server/server-client based interactions, ie. client A needs updates when client B does something, without disrupting client C.
Phoenix LiveView is an incredibly powerful tool, especially for someone like me who absolutely does not like Javascript frameworks rendering everything in a virtual DOM, before passing that *word I’m not allowed to say* to the browser to actually make sense of it.
LiveView will introduce a little lag in your app - in this case I would like to lie, but honestly, if your server is in California, and the client is in India, there is just no way to cut out that lag - but it will also introduce an absolute truth! If the has a certain state, based on the database, then your view will reflect that!
And due to the nature of Elixir, you can send messages to the process triggering something, or you can build subscription based client updates entirly in Elixir!

Act four: How Rick and Morty is not a show for high IQ people, internet exists you know
or the final verdict

Phoenix is very flexible in how you use it, the most important part is probably you remember: Phoenix is not your application, it is just a transportation layer! It works well enough with DDD, but it does not strictly follow the rules.
As of now, you will have to find your own style - but that can also be a good thing! I came up with most of the things I do myself, and later discovered that I was actually following something someone else described in a book. It works for me, and it feels natural to me, and Phoenix is very liberal in what it allows or what harness it puts on the programmer. It can be a curse or you can just take the freedom you have and develop an app you are happy with.

10 Likes

For the first approach, I will type out the mock data inside the controller. Only when I am pretty sure of how the data model is going to look like, then I will start writing the code for the contexts for CRUD purposes. This is where generators come into place.