[erlang-questions] An Erlang 101 question

Garrett Smith g@REDACTED
Fri Jul 17 04:24:03 CEST 2015


A few observations...

I think the problem you're looking at resembles the data server in the
e2 tutorial:

http://e2project.org/tutorial.html

In the tutorial, you start out creating a super simple data interface
for reading and writing values:

http://e2project.org/tutorial.html#data-access-api

Later, to make that interface available to the system, you wrap it in
a registered process (e2 service):

http://e2project.org/tutorial.html#data-server

That's an indirection, but the step serves an important role: to
implement a lifecycle for the data access layer. By lifecycle I mean,
what do you need to do start the data facilities? What do you need to
access them? The indirection gives you process isolation as well and a
chance to recover from failure. It serves as the "data service" to
your app, wrapping the details that consumers don't care about and
working to make the facility as available as possible.

In your case, you could simply rename the modules from the tutorial this way:

    mydb_db -> contacts_dets
    mydb_data -> contacts

So call to contacts:get_contact/1 is a service front-end, which calls
e2_service:call/2 (or gen_server:call/2 if you want to type more
code). That sends a message from the client to the service process.
The message handler (handle_msg/3 in e2, or handle_call/3 for
gen_server) is called on the service process side and just passes the
Db reference (managed by the service) and the additional args
(provided by the client call) to contacts_dets, which implements the
dets specific backend.

Imagine this for your contact operations and you've got it!

As for these ideas about hiding record definitions, I think for your
case, right now, I would just forget everything there. It's not
helping you. It's really not a problem to let a record propagate
throughout your application. It might be a problem in time, but it's
not now. It might never be.

That said, working with records as a data structure IMO is a bit
painful and I'd be _tempted_ to use maps for attribute data in and out
of your contacts_dets module. By "attribute data" here I mean non key
values that tend to change over time. So adding a contact might look
like this:

    contacts_dets:update_contact(Key, Attrs)

Attrs here is a map, which contacts_dets can coerce into whatever
record it's using.

But this takes work and code and what does it get you? It's nice that
you don't have a gnarly record definition in use everywhere, but the
map code will look similar. Is it easier to maintain? I doubt it. If I
were you right now, I'd just let this record leak everywhere. You have
more interesting problems to solve.

So quick summary of my thinking:

- Copy the e2 tutorial code and rename the modules as per above

- Replace the get, set, and del functions with the operations that you
want for contacts

- Happily use this facility in your application for all your contact
related needs!

On Thu, Jul 16, 2015 at 5:46 PM,  <lloyd@REDACTED> wrote:
> Hi Garrett and Martin,
>
> Yes, I'm using dets temporarily until I decide what database to use. All the dets-exposed functions are going through an api, e.g.:
>
> contacts:get_contact(Email) ->
>    contacts_api:get_contact(Email).
>
> contacts_api:get_contact(Email) ->
>    dets_contacts:get_contact(Email).
>
> get_contact(Email) ->
>    open_db(),
>    Result = dets:lookup(?contacts, Email),
>    close_db(),
>    Result.
>
> Sort of in the spirit of your suggestion to get something working then sweat the edge cases.
>
> I'm not confident that the second level of indirection is necessary, but it reflects my current understanding of how to isolate record structures to one module only. I'm using Jesper's repr/2 code to access values.
>
> Re: the idea of exposing db calls through a gen_server or e2 service, would this apply only to dbs such as dets that don't have transactions? Or is it good practice regardless of of the db back-end?
>
> As my last few posts may have hinted, with the help of Jesper and others I'm struggling to understand conventional Erlang practices that I, at least, have not found well documented. That's the problem of being a one-man-band. I can't call in my supervisor when I'm stuck. But folks have been extraordinarily generous with tips and information for which I'm profoundly grateful.
>
> Martin--- thanks for your suggestion. I'll adopt it.
>
> Thanks to all,
>
> Lloyd
>
>
> -----Original Message-----
> From: "Garrett Smith" <g@REDACTED>
> Sent: Thursday, July 16, 2015 5:37pm
> To: "Lloyd Prentice" <lloyd@REDACTED>
> Cc: "erlang-questions" <erlang-questions@REDACTED>
> Subject: Re: [erlang-questions] An Erlang 101 question
>
> Hey, I'm all for a direct solution, but this opening and closing of
> the db for each read feels a little cavalier, even for me ;)
>
> I would use a gen_server (e2 service) to maintain the open db,
> flushing on each write to guard against data loss if the VM is
> shutdown abruptly.
>
> But to answer your question, a more canonical interface here would be
> to return either {ok, Contact} or error (mapped from [Contact] or []
> respectively) - or just return Contact and generate an exception if
> the contact doesn't exists. The form depends on what you want this
> function to do. See dict for an example of an interface that provide
> both interfaces - and lets the user decide.
>
> You're right though, exposing the dets interface here is wrong,
> morally speaking.
>
> On Thu, Jul 16, 2015 at 2:02 PM,  <lloyd@REDACTED> wrote:
>> Hello,
>>
>> I have several functions that look like this:
>>
>> get_contact(Email) ->
>>   open_db(),
>>   Result = dets:lookup(?contacts, Email),
>>   close_db(),
>>   Result.
>>
>> The implication is that the function returns and empty list, [], if it fails to find a record, otherwise it returns a list containing the record.
>>
>> This makes it easy to distinguish the failure-to-return a record condition, but means that every function that depends up on get_contact/1 needs to do so and know what to do in that case. But that begins to feel like defensive programming.
>>
>> I can see several ways of dealing with this problem, but it occurs to me that there must be a conventional approach. What might be... what?
>>
>> Thanks,
>>
>> LRP
>>
>>
>>
>> _______________________________________________
>> erlang-questions mailing list
>> erlang-questions@REDACTED
>> http://erlang.org/mailman/listinfo/erlang-questions
>
>



More information about the erlang-questions mailing list