matiso

matiso

My use case is the following. I have an umbrella application that clearly separates the DB access layer from the business logic. By doing this, I don’t want to directly expose the DB resources to the other applications.

Instead, what I want is to have an edge interface that will expose the DB resource, and a receiver that will do the conversion from the “DB resource” struct, to an internal POEXS (plain old Elixir struct).

I know it might be overcomplicating things, but I want to see how far I can get with this approach.

What bothers me is that I don’t see a way to directly transform StructA into StructB.

So if I have:

%DB.FooResource{name: "bar"}

And I’d like to convert it into:

%MyApp.Foo{name: "bar"}

What I’d need to do is to convert the first one into a map, and create a struct from that map.

data = db_resource |> Map.from_struct()
struct(MyApp.Foo, data)

A bit cumbersome in my opinion, especially if you get an array of those.

Is there a better way of doing this, or is it cumbersome, because it’s a wrong approach?

Showing Posts 1 to 8

OvermindDL1

OvermindDL1

Probably this. ^.^;

But still, do they all have identical fields? If so then you can just swap the __struct__ value on it:

iex(15)> defmodule Tester1 do defstruct name: 1 end
{:module, Tester1,
 <<70, 79, 82, 49, 0, 0, 5, 40, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 143,
   0, 0, 0, 13, 14, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 101,
   114, 49, 8, 95, 95, 105, 110, 102, 111, 95, ...>>, %Tester1{name: 1}}
iex(16)> defmodule Tester2 do defstruct name: 2 end
{:module, Tester2,
 <<70, 79, 82, 49, 0, 0, 5, 40, 66, 69, 65, 77, 65, 116, 85, 56, 0, 0, 0, 143,
   0, 0, 0, 13, 14, 69, 108, 105, 120, 105, 114, 46, 84, 101, 115, 116, 101,
   114, 50, 8, 95, 95, 105, 110, 102, 111, 95, ...>>, %Tester2{name: 2}}
iex(17)> t = %Tester1{}
%Tester1{name: 1}
iex(18)> tt = %{t | __struct__: Tester2}
%Tester2{name: 1}

That only works if they have identical fields though, but it is the fastest way for sure.

Be sure they match perfectly, if their fields don’t match or the struct name is wrong then things start to fail in interesting ways:

iex(19)> %{t | __struct__: Tester3}
%{__struct__: Tester3, name: 1}
LostKobrakai

LostKobrakai

Another way would be using struct(MyApp.Foo, Map.from_struct(db_struct))

axelson

axelson

Scenic Core Team

I’d think that you’d probably want to create a MyApp.Foo.from_foo_resource/1 to encapsulate the knowledge of how to transform a %DB.FooResource{} into a %MyApp.Foo{} rather than potentially hard-coding that knowledge in multiple places. It might look something like:

defmodule MyApp.Foo do
  defstruct [:name]
  
  def from_foo_resource(%DB.FooResource{} = foo_resource) do
    struct(MyApp.Foo, Map.from_struct(foo_resource))
  end
end

Then if you have a list of %DB.FooResource{} you can do

resource_list = [%DB.FooResource{name: "a"}, %DB.FooResource{name: "b"}]
foo_list = Enum.map(resource_list, &MyApp.Foo.from_foo_resource/1)

Although if all you have is a one-to-one mapping this entire approach sounds like it is overkill (but it is an interesting experiment). I’d imagine if you’re combining multiple DB resources into one representation then something like this definitely makes sense. Or if you’re doing some type of CQRS system.

13
Post #3
matiso

matiso OP

I like this idea, thanks!

Well, that’s the thing. I like to imagine the different OTP applications which compose my Elixir applicaiton as isolated modules, that only interact with each other through explicilty enabled interfaces (just like in a MicroService architecture, but all within Elixir).

Having the same exact keys for both looks like an overkill, but is actually enforcing a way to decouple the pure application logic from the DB and persistance layer. If I just use the DB resource, I’m coupling 100% one application with the other - and I really want to avoid that.

ben-pr-p

ben-pr-p

I’ve used Remodel with success.

It’s useful if your fields are different but there’s someoverlap (you specify the fields you want, and if they’re in the input struct they simply transfer), if there’s some direct field to field translation, and / or some more complex logic for computing the new fields.

anthonator

anthonator

@matiso we’re doing something similar with an umbrella app.

Here’s what we came up with.

defmodule Context.Primary.Helpers.Entity do
  def new(nil, _entity) do
    nil
  end

  def new(%_{} = struct, entity) do
    struct
    |> Map.from_struct()
    |> new(entity)
  end

  def new(data, entity) do
    struct(entity, data)
  end
end

Pretty straightforward. This does break down when you need to compose something from multiple sources.

vschroeder

vschroeder

We are doing the same here, so when retrieving data from DB (usually JSON), we like to wrap it in an internal struct that our modules will know about, but for other needs, we perform a conversion to another struct (passing data to an API, saving to another destination, outputting to the client, etc). To reduce verbosity, we expose a function from/1 on the destination struct, which pattern matches one or more source structs and, potentially, a raw JSON map.

For example:

defmodule Whatever.DB.User do
  defstruct [:id, :first_name, :last_name, :hash, :permissions]

  # This one is to create a struct from JSON map
  def from(%{
    "_id" => id,
    "firstName" => first_name,
    "lastName" => last_name,
    "hash" => hash,
    "permissions" => permissions,
  }) do
    %__MODULE__{
      id: id,
      first_name: first_name,
      last_name: last_name,
      hash: hash,
      permissions: permissions,
    }
  end
end

defmodule Whatever.Public.User do
  defstruct [:id, :full_name]

  def from(%Whatever.DB.User{
    id: id,
    first_name: first_name,
    last_name: last_name,
  }) do
    %__MODULE__{
      id: id,
      full_name: "#{first_name} #{last_name}",
    }
  end
end

An so on. The pattern matching is the coolest thing ever for this kind of structure, as it does not only the matching to the correct struct, but also validates the incoming structure. This can, of course, get much longer and as complex as we need, with default values, partial matches and such.

It’s a bit repetitive and quite some boilerplate, but when using, it’s very nice:

alias Whatever.DB.User, as: DBUser
alias Whatever.Public.User, as: PublicUser

user = DBUser.from fetch_json_from_db_somehow
public_user = PublicUser.from user

This gets even better when implementing protocols for JSON encoding/decoding!

rogerdff

rogerdff

I was searching for the same decoupling, defining in my umbrella project an ADT Interface (Abstract Data Type).

And using direct Phoenix PubSub and RPC for data exchange through the ADTs types.

Works perfectly, just dont know yet how that will perform when in Prod Env.

— All posts loaded —

Where Next? Top

Trending in Questions Top

RSP87
I’m working on a project that simulates the bumbl example in the programming phoenix book. It acts almost like an email client. We have a...
New
nseaSeb
Hello, I know there is an approach for handling lists that allows for optimized traversal, but I can’t recall the specific method (somet...
New
RemyXRenard
I’m seeing that a list inside a Kino.DataTable will be interpreted as a charlist, even if the Kino.configure() is set to charlists: :as_l...
New
velrest
So my question is quite simple and i have found no conclusive answer on forum, google or AI. Should we use :erlang.float for Integer to ...
New
brecabral
Documentation While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
samoloth
Hi, I’ve just set up an application with ash_authentication. There is only magic link strategy for now, so there is no confirmation add o...
New
FlyingNoodle
If a change or preparation module uses Ash.Changeset.get_argument/2 or Ash.Query.get_argument/2 (or any of the other get_argument functio...
New

Other Trending Topics Top

mudasobwa
I am happy to introduce the very α version of the new programming language compiled to BEAM. Welcome Cure. It has literally three kille...
New
marciok
Hi there! We created Gust: A task orchestrator inspired by Airflow. For those who have never heard about Aiflow, it’s a Python-based wor...
New
jimsynz
Beam Bots (or just BB for short) is a framework for building fault-tolerant robotics applications in Elixir using familiar OTP patterns. ...
New
Dmk
Xamal is a deployment tool for Elixir apps that deploys native releases to bare metal servers over SSH. It’s a port of GitHub - basecamp/...
New
netoum
Corex is an accessible, unstyled UI component library for Phoenix that integrates Zag.js state machines using Vanilla JavaScript and Live...
New
webofbits
With AI doing more of the implementation work, I’ve been wondering how much coding I should deliberately keep doing myself. My main conc...
#ai
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews