zoedsoupe
How about "DTOs" on controllers? (Data Transfer Objects)
In our app, we are trying to apply a hexagonal architecture. We have the concept of internal schemas, generally ecto embedded schemas or schemaless changesets that perform some validation or data transformation. Its easier to transport internal data with this logic.
The workflow:
- App receives an incoming message (API, Kafka)
- Controller uses an Adapter to parse an internal schema
- Controller passes this schema to the context
- Render response
However, we have some problems:
- We have several external providers (Kafka, other APIs, frontend) that can have different fields.
- We would need a way to create different versions of a schema, for an API
- We would like to move this validation with schemas more to the edge of our web layer
The idea is somewhat the same as a DTO (Data Transfer Object), but it would be only flat maps only to do validation and maybe reject the invalid incoming message.
Does anyone make something similar to this? Does this make sense at all?
Most Liked
kokolegorille
You might find this highly related ![]()
and
I prefer CQRS/ES because I think it fits well with signal and processes.
This book, although not in Elixir, but in F#, is in my recommendation list
beepbeepbopbop
The biggest takeaway I’ve personally found implementing these sort of patterns in Elixir (or probably in any functional language), is the concept of “functional core, imperative shell”. I would go on to say that it is the overriding principle in your classic hexagonal architecture, CQRS included (especially in Commanded aggregates).
“Functional core, imperative shell” as a concept does not require as much ceremony as one would think. Certainly nothing on the level of classic OOP languages, although there are a couple of discussion points to have had.
One such argument is the value of persistence and transactionality in your core application. Storage is such a key component to a vast majority of applications that they are intrinsically link to the core. Proponents point out that this is a serialisation process, an imperative shell that needs to be completely isolated from the core system. In the book Designing Elixir Systems with OTP: Write Highly Scalable, Self-Healing Software with Layers by James Edward Gray, II and Bruce A. Tate, it pushes it to a different layer as implied in the table of contents:
- Assemble Your Components
- Add Persistence as a Boundary Service
In this case, your core systems have no idea about database structures and this requires an encoding/decoding layer that you’ll have to write. While somewhat tedious, it is “technically” correct and if there’s one word to summarise the process of implementing application with the “functional core, imperative shell”, tedious could be a word one would use.
Maybe you don’t really need all this and you place transactionality as part of the domain itself. This certainly makes the rules simpler. In that case, you can model your code in a “light” style of functional core that contexts give you. An excellent primer to get your head in this zone are a series of articles by Sasa Juric:
- https://medium.com/very-big-things/towards-maintainable-elixir-the-core-and-the-interface-c267f0da43
- https://medium.com/very-big-things/towards-maintainable-elixir-boundaries-ba013c731c0a
- https://medium.com/very-big-things/towards-maintainable-elixir-the-anatomy-of-a-core-module-b7372009ca6d
Just don’t use it as the “source of truth” for all things related to code structure and standards. It’s a set of very well explained decisions, but it would be wrong to conflate it as the “canonical way” to build Elixir applications.
kokolegorille
Your structure is not conventional, and it looks You are trying to do Ruby in Elixir, but Elixir is not Ruby.
Changesets are the usual way to deal with DTO, and You might have many in the same schema. For example one for kafka, one for another provider, etc. Each one with custom validation, and casting rules.
Is there a reason to have business logic in the controller?
Last Post!
kokolegorille
You might think of your data first. And functions to transform them. Functions are first class citizen.
input → function → output
If You can remember input type, and output type of your functions, You can build pipelines of composable functions.
Then, You might think of where You want to add concurrency to your application, because You are on the BEAM.
This is not mandatory if You use Phoenix, because it’s done for You.
In Your case…
%AnyStruct{}
|> AnyStruct.changeset(dto)
|> Repo.update (or insert)
-> {:ok, %AnyStruct{}} | {:error, changeset}
Popular in Discussions
Other popular topics
Latest Phoenix Threads
Chat & Discussions>Discussions
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #deployment
- #library
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #channels
- #elixirconf
- #exunit
- #discussion
- #code-sync
- #javascript
- #podcasts
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ecto-query
- #elixir-ls
- #phoenix_html
- #iex
- #blog-post
- #graphql
- #genstage
- #ai
- #websockets
- #supervisor
- #elixirconf-us
- #advent-of-code
- #distillery
- #processes
- #forms
- #api
- #metaprogramming
- #hex
- #security









