Iex.new

Iex.new

Hello !

As project to start to learn Elixir I have decided to begin with a small library.
This library has for goal to supply functions to help to communicate with the API of https://carbone.io
Carbone.io produce documents in different formats based on a (json) template (language) and the data supplied in the same format.

“Design” choices:

  • http client : Finch
  • system env-variable must provide the API-Token for the authentication
  • system env-variable can provide the base URI of the the Service
  • system env-variable can provide the version of the service to use

As beginner I’m unsure if Finch is good fit for the library instead of Tesla

Units tests:

I have started to write a few tests and I’m not sure how to test the following functions because they call Finch functions:

  • create_document/3
  • add_template/2
  • render_template/2

I would really appreciate if you can review my code and also give me some advices how to improve it.

The Elixir code for the library is available on my Github repository.

An example of “Carbone.io” template can be found here .

Thanks a lot ! :smiley:

Showing Posts 1 to 10

hst337

hst337

Hi, it’s a nice code for the first project. Here are the things I’ve noted during reading

Not important things

  • mix format. Use spaces or tabs, not both

  • Translate this

              response
              |> get_template_id_from_response
    

    to get_template_id_from_response(response)

  • Do not refer to the functions in module like Carbonex.function, use just function.

  • Translate

      case decode_json(response) do
        {:ok, map} -> map["data"]["templateId"]
        _ -> nil
      end
    

    to

      case decode_json(response) do
        {:ok, %{"data" => %{"templateId" => id}}} -> id
        _ -> nil
      end
    

    By the way, the code above occurs twice, so I’d suggest to DRY it.

  • You can pipe into case, so instead

    result = Jason.encode(data)
    case result do
    

    you can write

    data
    |> Jason.encode()
    |> case do
    

Important things and design

  • More tests. To test the HTTP interactions, you can use awesome Bypass library.

  • Do not use System.get_env. I’d suggest just to create some structure which holds url, version and token for the carbone.io. This will make the code more pure (in functional sense) and easier to test

  • Provide function which starts finch with given url, version and token.

  • Learn specs. Proper and precise type annotations are better than any documentation.

  • I think that get_document function should return the document, but not the HTTP response structure.

  • create_multipart function checks the existence of a file, while it has already been checked in add_template function

Iex.new

Iex.new OP

Hi hissssst,

thanks a lot for reviewing my code!
I’m working on the implementation of the differents points you have described.
I will come back here when I think I’m done with! :slight_smile:

BartOtten

BartOtten

Nice review! One thing to note is that piping into a case has been described as a bad pattern. The suggested solution is to extract the cases into a function (with multiple heads).

Benefits:

  1. Function name can be explanatory about what’s happening
  2. Easier to test the ‘cases’
hst337

hst337

Yeah, I’ve seen @keathley 's blog post before. But to be honest, I think that it is so minor, that it’s not even worth talking about. I’ve included it into list of not important things more like a “you can do this” than “you should do this”. And the benefits you’ve described are so general that they’re applicate to every pipe.


And considering the post, it’s so much opinionated, so I don’t think that it’s fair to reason about it in a terms of a good or bad practice. It’s more like a set of personal preferences.

I mean, I don’t like tap and then even if they increase readability. And I don’t like Access, because it has problems with structures. But I’ve seen a lot of great projects where people have used Elixir in a very weird way which I do not like.

cjbottaro

cjbottaro

Check out this article on mocking via explicit contracts by the man himself: Mocks and explicit contracts - Dashbit Blog

The use case in the article is about testing external APIs.

sodapopcan

sodapopcan

Piping into case is absolutely a matter of taste and I say do it if you want, but I don’t agree that the presented logic applies to any pipeline. I find it significantly clearer when the data structure that goes into a pipeline is the same data structure that comes out the other end. There are, of course, always exceptions, but that’s my rule of thumb. The example from the article is subtle, but in the pipeline version we never explicitly see that the data was ever transformed into a changeset, so it’s nice to explicitly call that out.

Since Chris is already in this thread, maybe he can confirm if I’m on the right track or not, but I’m not gonna tag him as I’m sure he’s sick of talking about it :upside_down_face: In any event, that’s my reasoning for not case-piping but again, it’s all a matter of taste!

evadne

evadne

Hi, sorry for adding to such a long thread

[1]

I do not like APIs that require string keys / other than camel case as input, such as the request-body using convertTo, for example

request_body = %{"convertTo" => "pdf", "data" => data}
Carbonex.create(finch_name, "path/to/template.odt", request_body)

should ideally be

Carbonex.create(template_path, data, convert_to: :pdf)

In general if you read Elixir source code a lot you will find that folks usually use camel case only, we do not do title case much, so it would be good to make it consistent.

Also exposing Finch as your HTTP API is probably not ideal (where does finch_name come from, etc) better to make it configurable if you have to but otherwise have a default API that does not require plugging this information.

Case in hand ExAws uses hackney but doesn’t ask you what pool, it just uses the :ex_aws pool.

[2]

README needs work, show me how to actually use the library and I don’t want to see stuff that is standard such as “Documentation can be generated with ExDoc and published on HexDocs” which is just part of the original boilerplate.

The README should describe what is available in the library and how to use it. If I copy and paste each chunk of code into iex it should execute, which it does not right now. Also it would seem that carbone.io would require an API key, but the way to configure it is unclear. This must be addressed.

I am not to impose a rigid structure, but it is my opinion that for any library, the README should include the following:

  1. Name of the library
  2. Succinct description of the problem it solves (do not write a nerd essay, keep it under 100 words)
  3. If the library solves a common problem, explanation of the problem and analysis of all equivalent solutions, justification for using this library over others (this can be skipped if writing first-party platform SDKs, otherwise especially if the library is algorithmic or data-intensive, it would be good to add some benchmark results)
  4. Installation & configuration (the “add this to mix.exs” section comes in here, also any environment variables & key configuration should be included, once I’ve read this section I should have everything needed to get the library integrated)
  5. Some examples / usage (can be part of hexdocs instead)

And if you are nice then add changelogs, acknowledgements, reference, etc.

Link to hexdocs should be possible to find within the first 1 second of looking at the README.

[3]

For symmetry reasons, you might want to instead call the function Carbonex.render vs Carbonex.create the latter has no direct counterpart in the API of the service. In general when writing an Elixir SDK for a service you should match the name of whatever method/function published by the service.

So I would expect something like this

@type option :: {:convert_to, :pdf}
@type data :: binary() | map()
@spec render(template_name :: string(), data :: data(), options :: list(option())) ::
  {:ok, document_data :: binary()} | {:error, reason :: term()}

Note how we are typing the option as a 2-arity tuple, this is so the options can be passed in as a Keyword and its type will be checked. If you write it as a map it will be messier.

You can check out Stripe and how they intersperse different SDKs together alongside with cURL, the bottom line here is that integration engineers are very busy and you need to make sure your Elixir SDK is aligned with the service as much as possible.

[4]

carbonex/templates/Sample_template.odt

Wrong capitalisation, should be

carbonex/templates/sample_template.odt

Sweat the small stuff

[5]

See Carbonex requires CARBONE_TOKEN to be set in the environment, I would think that this is an Elixir anti-pattern and folks would be happier if you wrapped the configuration in a context object etc.

Read Library guidelines — Elixir v1.21.0-dev for a bit and again compare how ex_aws is configured… Will users want to use more than 1 token with your library? If no then you can keep what you have, or change it so you get it from Config.

Otherwise, you have to do something like this

defmodule Carbonex.Environment do
  @enforce_keys ~w(token)a
  defstruct token: nil
end

defmodule Carbonex do
  def render(environment, data, options) do
    …
  end
end

Which will be used thusly

environment = Carbonex.Environment{token: "…"}
Carbonex.render(environment, …)

Again use own judgement

[6]

Lastly from a maintainability point of view, does your platform publish OpenAPI v3 Specs? If so, consider code generation vs hand-rolling code

Iex.new

Iex.new OP

Thank you to all of you to took time to read my code and to give me advices for improvements! I have a lot to do and to learn :slight_smile: !

@evadne :

convertTo come from the API of Carbone.io. What do you mean by “my platform” ?
As you can see on their API-Documentation page they expect a convertTo in the request body.

At work for a project I had to consume their service. I have written a lib in PHP for one of our projects and for me it was I guess the best project to start with to learn a new language.

I am aware of code generators, we use them for some Web Services at work, but I think this is not the right way for me if I want to learn a new language.

I don’t have really experience(s) with functional languages other than one online course about Elixir for 2 years I guess :slight_smile:

msimonborg

msimonborg

I think the idea (which I agree with) is that an Elixir client library should do the job under the hood of translating the service API’s expectations to a more idiomatic Elixir interface.

This

request_body = %{"convertTo" => "pdf", "data" => data}
Carbonex.create(finch_name, "path/to/template.odt", request_body)

is sort of a thin wrapper around finch, where this Carbonex.create(template_path, data, convert_to: :pdf) abstracts away the underlying service and HTTP implementation details.

Iex.new

Iex.new OP

@msimonborg ah… I understand! thank you for clarify this point!

Where Next? Top

Trending in Discussions Top

AstonJ
As the title says, please share what you’ve been up to with Elixir. Whether that’s been learning it, looking into it, making stuff with i...
2977 92995 915
New
caslu
I want to open this thread for you all to discuss and help those who really like Ash but are still hesitant to use it in a real project. ...
New
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
GES233
I’m posting this in response to Jose’s recent tweet (Cr. link) : People are sleeping on Elixir for a coding harness: Hot-code swappi...
New
marciol
It would be helpful to have a list of companies worldwide that hire engineers without prior experience in Elixir. Often, it can be quite ...
New
durvia
Anyone running long-lived stateful processes on BEAM? We’re building an AI agent runtime and would love to compare notes. We’re a small ...
New
nseaSeb
I’ve just put together a small POC exploring PDF inspection from Elixir/Phoenix: The idea is pretty simple: drag & drop a PDF in a...
New

Other Trending Topics Top

garrison
Hobbes is a low-level distributed database for the Elixir programming language. Hobbes provides a simple, safe, and scalable storage lay...
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
Damirados
Hello everyone. After busy few months I am happy to announce v0.1.0 of Emerge & Solve. They are GUI (Emerge) and State management (S...
New
wintermeyer
There are three potential reasons for members of this forum to have a look at https://vutuv.de You are tired or annoyed of LinkedIn. Yo...
New

We're in Beta

About us Mission Statement

Options

Thread Display Mode




Thread Preview

Skip Thread Previews