Elixir Reflection

Hello,

Actually I’m making a dynamic TCP protocol for a game with elixir, all the messages are already writed on another language I just need to handle every message in elixir and send back an answer to this message, actually my “model” to handle recieve its like this:

Get data bytes from the socket |> Read the message Id, the message length and main data |> contruct the message by reflection
(So here I decided to use a existing solution created by hanshine (It took me a long time to understand the code):

  1. Generate the messages at compile time based on a file, like this:
    https://github.com/McEx/McProtocol/blob/master/lib/packet.ex
  2. And get contruct it with the message id, like this:
    https://github.com/McEx/McProtocol/blob/master/lib/packet/in.ex
    )

But I also found a simplistic solution that is use Kernel.apply/3 and String.to_existing_atom/1, I’m not sure what of the two approach are better for this kind of problem (if you know a better solution feel free to tell me please).

After this “reflection” I need to call another reflection for handle the logic of this message and return the messages to send, the problem is that are so many messages sended by the client, to organize it I need many files to keep the project clean, so the project can looks like

handlers(folder)
   ->login(folder)
        |> login_handler.ex
   ->game(folder)
        |>fight(folder)
            |>fight_handler.ex
        |>inventory(folder)
        |>entities(folder)
.....

How I can handle this kind of reflection? For example call the by an id like this (I dont know if is a valid elixir code this is a code more like c#) (this is like call de definition by attribute, but I dont find something like this on elixir)

# handlers/connection/ping.ex
@messageid 1 #I dont know if its possible but its a code more like c#, so just need the message id and call with elixir in some way no matter the directory/module name(dunno if its possible)
def handle_ping_message(state, message) do
    {:send, PongMessage}
end

or like a pattern matching like this

# handlers/connection/ping.ex
def handle_ping_message(message = %{PingMessage}) do # sended as argument and pattern match over all proyect
   {:send, PongMessage}
end

Again, I don’t know if this is possible (or correct) in elixir, just I illustrate how it looks in my mind, I want to know how implement something like this on elixir

Thats my problem! I hope someone can help me to solve this and a way to improve the actual “solution” that I have

Greetings!

Ok I can’t help but be reminded of this

@chadfowler
The older I get, the more I realize the biggest problem to solve in tech is to get people to stop making things harder than
they have to be.

… follow you so far but then …

I think you may be getting a bit hung up on the whole “reflection” business (always seemed a bit “smart aleck” to me anyway - if you want your code to be data use a Lisp) - and then as a result be possibly overcomplicating things.

Back in the dark ages when I was working in C this kind of problem was solved quite simply by:

  • Beginning the message with a fixed width discriminator field - i.e. the bit pattern in here identified the kind of message that it was. If you have lots of different types of messages you needed a wider field.
  • The second fixed width field would identify the total size of the message. While bytes as a unit would seem natural you could actually chose whatever unit you want - just be consistent.

So the size makes it possible to grab the entire message regardless of what the message actually contains and the discriminator is then used to select the appropriate dispatch function. The entire message is then simply handed to the correct dispatch function which

a) knows the internal structure of the message to extract the necessary information
b) knows where to dispatch this information to, in order to get it processed

Given the extensive support of binaries in Erlang I’m sure that there is plenty of Erlang code that operates in exactly the same way and that Elixir is quite capable of handling it. Also if the discriminators can be legally used as Map keys - you could load a map at runtime with discriminator/dispatch function pairs to select the correct dispatch function without writing a huge static case expression.

This is probably the biggest issue that you are facing - it sounds to me like your messages are being constructed “auto-magically” by some wizard based on some data structures as they exist in some source language. In the web services world this is called code first/contract last development - and it is bad news. Contract first/code last is always the way to go even if it is less convenient at first - design the messages first and independent of the implementation languages that may be used. Typically that is the best way to ensure that the messages are simply constructed and can be read in any implementation language.

To me this sounds you are still suffering from a “synchronous mindset” and because of it your design is having problems. Move towards an asynchronous processing model:

  • One part of the system is responsible for processing incoming messages. It’s only responsibility is to make sure that the incoming data is routed to the place that needs to process it. It is not concerned at all with any potential responses.

  • Once the data is processed it is routed to an entirely different part of the system which sends off the results (back?)

Essentially you need to start following Scott Wlaschin’s advice (“Thinking Functionally”) and move away from the request/response model of using functions and processes and think more in terms of input/output and “railway oriented” programming.

3 Likes

Thanks for your answer @peerreynders! I took all my time to explore every example, I need to say thanks help me to think more functinal (specially the monads), but sadly I need to say that I still without figure out a final Elixir way for my problem, I’ll explain:

Here is not a problem, I already have my reader/writer for every data over the socket, it basically contains:

  • Message id (unique id for every message, every one has a different work on the server and call for something different) [always an ushort field, integer if its elixir]
  • The message length
  • The rest data, this remain data is used to construct the “module” based on the MessageId, this “module” contains primitive properties and class properties as well (this work in a OO language, but this abstractions can easily move to elixir, simply moving the abstract class to the result module), so here…

…is the problem, the total messages in this case are 1040 without counting the types (Serialized classes inside de message) and the server sends 705 of those messages and recieve 335 from the client, handle the deserializer for all those messages + the handlers for every messages its insane exausting and hard to mantain!

This is an interesting proposal can you give me an example about it? I exactly dont understand what do you mean with “discriminators” on elixir

I’ll keep this in mind after solve the dispatch problem, thanks!


So in summary my problem is “how to construct a module based on a identifier without using a huge case expression (?)” and “how to call a handler (module function) based on this result module (or the message id) without using a huge case expression (again)”


Notes: I just give my previous examples based on my experience on C#, this is the “C# like” way to handle this solution, but now I’m really sure that this is not the solution for Elixir, so I want to know what is the best way here ^^ I did this project specifically to know the amazing Elixir ^^, this challenges are the best to study the language :smiley: (in my opinion ^^), I’m really happy to get answers that makes me learn more about Elixir, thanks!

Greetings!

Well, I think this is your first problem right here - why do you need such a large number of types or kinds of messages? I think it is time that you have a good look at your message protocol because it certainly sounds like it has gotten out of control some time ago.

1 Like

Hahahaha sadly I can’t do it its already implemented, I cannot change nothing from this code, just code the server side ^^’

Ok, due to the inflexibility on this part - if you are doing this just as a learning experience - step back and walk away right now …

Because if you press forward you may have already noticed that there is nothing but an unrewarding exercise in utter frustration ahead of you - which isn’t going to be worth the effort for the “learning experience”. This has nothing to do the any lack of capability on Elixir’s part but everything with how the existing system has been designed to be irreversibly coupled with it’s initial implementation environment - most likely for the sake of a few shortcuts here and there that likely did not help the overall system design.

It is starting to sound a lot like these messages are simply conveyances for some arbitrary serialized object aggregates that can only work because both the client and the server were originally implemented on the same platform and language that enabled them to share an identical class hierarchy - causing the worst kind of implementation coupling.

Now if for some reason an “unrewarding exercise in utter frustration” is somehow the lesser of two evils, the DDD approach would be to create an anti-corruption-layer. But you would need to understand on a very fundamental non-Elixir or non-C# level what the actual essence of these 1040 “types” of messages actually boils down to - so that you can take that insanity and distill it down to some sanely structured information that you can make some sense out of. You will need this fundamental knowledge to build a “layer” the turns insanity into sanity.

Essentially your project seems to suffer from a problem that also came up in this topic - you cannot simply take an object-oriented solution implementation and start translating that implementation into Elixir - typically a solution redesign is required - not because Elixir isn’t good enough but because sadly many OO implementations let their OO ways leak into aspects where they are at best not beneficial or at worst extremely detrimental.

4 Likes