yojimbo87 / ArangoDB-NET

C#/.NET/Mono driver for ArangoDB
MIT License
107 stars 66 forks source link

Support for Foxx services #38

Closed thierry-github closed 7 years ago

thierry-github commented 8 years ago

hy,

is there any support for Foxx Micro services in the driver ? Any documentation ?

regards.

yojimbo87 commented 8 years ago

Hi,

currently there is no support for Foxx in the driver.

thierry-github commented 8 years ago

Hi, Any plan to support it in the next release ?

Envoyé depuis mon appareil Samsung

-------- Message d'origine -------- De : Tomas Bosak notifications@github.com Date : 2016/09/26 10:06 (GMT-05:00) À : yojimbo87/ArangoDB-NET ArangoDB-NET@noreply.github.com Cc : Thierry Cot Thierry.Cot@facteurk.com, Author author@noreply.github.com Objet : Re: [yojimbo87/ArangoDB-NET] Support for Foxx services (#38)

Hi,

currently there is no support for Foxx in the driver.

You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/yojimbo87/ArangoDB-NET/issues/38#issuecomment-249567382, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AG7cpYL1asgY5nqqsjFlx45rLqhPVkwBks5qt8a1gaJpZM4KGZ44.

eNeRGy164 commented 8 years ago

What would you expect of a Foxx driver?

For example, this is my own code calling my Foxx application "tree", no need for a special class there.

        var treeUrl = $"http{(connection.Secure ? "s" : "")}://{connection.Url}:{connection.Port}/_db/{connection.Database}/tree/{userId.ToUpperInvariant()}";
        var authorization = $"{connection.UserName}:{connection.Password}";

        var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes(authorization)));

        var response = await httpClient.GetStringAsync(treeUrl);

        var tree = JsonConvert.DeserializeObject<Tree>(response);
yojimbo87 commented 8 years ago

First I would like to finish coverage of graph API for the next release and then I can look into Foxx. Question in the meantime - how would you like the Foxx API to look like in the driver?

thierry-github commented 8 years ago

@eNeRGy164 , tks for the sample code. @yojimbo87 ,

my first thought was to access Foxs services through Database instance like :

var db = new ADatabase("database_Alias"); var result = db.FoxxServices ( foxx relative Uri , params , [http verb] ); if ( result.Success...

This way, we don't need to provide user identification nor database informations, They can be pulled from db instance and the driver could trap http exceptions and json object deserialization.

yojimbo87 commented 8 years ago

This might actually be rather easy to implement, so I will try to push it into the next release.

thierry-github commented 8 years ago

Ok thanks. Any idea of the release date ?

yojimbo87 commented 8 years ago

Middle of the next week if things go smooth.

thierry-github commented 8 years ago

Thanks

yojimbo87 commented 8 years ago

I will close this issue once it's implemented so that it's tracked.

yojimbo87 commented 7 years ago

I'm thinking whether it would be better to have one method which would invoke any foxx service request, for example like this:

var result = db.Foxx
    .Body(object data)
    .Request<T>(string relativeUri, HttpVerb verb);

or there would be dedicated methods for each HTTP verb like this:

var result1 = db.Foxx
    .Get<T>(string relativeUri);

var result2 = db.Foxx
    .Body(object data)
    .Post<T>(string relativeUri);

The API should also have some fluent methods, such as Body (as seen in the examples), because not all the requests have to send data to the services.

Another thing to solve might be how to construct the relative URIs. Users of C# 6 might have it easier with fancy string interpolation, but I think there are people who doesn't have this luxury, therefore they would have to use string.Format to construct the path with variables, so something in the form of:

var result1 = db.Foxx
    .Get<T>("/path/{param1}/to/{param2}", param1, param2);

might make the life little easier. What do you think?

thierry-github commented 7 years ago

For API invocation, i think that one method per verb is the best approach : it's more understandable in the code and it can evolves in a better way versus one single method and an invocation parameter list.

For relative URI construction, using string.Formatit's a common approach : i would go for it.

yojimbo87 commented 7 years ago

I created a separate branch where I implemented support for foxx services with tests for GET and POST requests (PUT, PATCH and DELETE verbs are also implemented). I haven't used foxx framework up until now and there might be stuff which would be good to add before it is pushed into the master, therefore it would be great if you could test it with some more complicated examples, because so far I only tried these simple services:

'use strict';
const createRouter = require('@arangodb/foxx/router');
const router = createRouter();

module.context.use(router);

router
    .get('/hello-world', function (req, res) {
        var data = {
            foo: "bar"
        };

        res.send({
            foo: "bar"
        });
    })
    .response(['application/json'], 'GET request.');

router
    .post('/hello-world', function (req, res) {
        var bodyObj = JSON.parse(req.body);

        res.send({
            foo: bodyObj.foo
        });
    })
    .response(['application/json'], 'POST request.');
thierry-github commented 7 years ago

i'm going to test it in the next week

thierry-github commented 7 years ago

I've done some tests in a real data environment ant it's generally working as expected. But in facing a strange behaviour and i can't figure out where the problem is.

I've made a FoxxService that returns Json content. Here's the router :

` router.post('/test', function (req, res) {
var w = { "data": [ { "_key": "2854219", "_id": "k_def/2854219", "_rev": "2854219", "_type": 0, "p_0": "6352b002-7749-4240-95ff-636230893577", "p_1": { "fr": "Élément de base", "en": "Root item" }, "p_10": false, "p_12": true, "p_13": 0, "p_14": false, "p_15": [ 0 ], "p_16": [ 1, 3 ], "p_17": 3, "p_18": 12, "p_19": false, "p_2": { "fr": "Élément de base duquel héritent tous les autres éléments", "en": "Root item from which all other items inherit" }, "p_20": true, "p_21": 0, "p_22": 12, "p_23": false, "p_302": false, "p_303": true, "p_306": false, "p_307": true, "p_308": true, "p_4": "2016-10-28T16:46:37.736Z", "p_6": "2016-10-28T16:46:37.736Z", "p_8": { "min": 0, "maj": 1 }, "p_9": false, "p_300": [ "k_dc_type/2854105" ], "p_7": [ "k_user/2854029" ], "p_5": [ "k_user/2854029" ] } ], "joins": { "k_dc_type/2854105": { "_key": "2854105", "_id": "k_dc_type/2854105", "_rev": "2857378", "_type": 0, "p_0": "dbf004b3-1203-4bd8-a56f-c9d8663246ae", "p_1": { "fr": "Abstrait", "en": "Abstract" }, "p_10": false, "p_100": 2, "p_12": true, "p_14": false, "p_18": 6, "p_19": false, "p_2": { "fr": "Une définition abstraite est utilisée pour représenter un type d'information sans toutefois permettre de créer des enregistrements pour cette information", "en": "An abstract definition is used to represent a type of information without allowing create records for this information" }, "p_20": false, "p_21": 0, "p_22": 6, "p_23": false, "p_4": "2016-10-28T16:46:37.671Z", "p_6": "2016-10-28T16:46:37.671Z", "p_8": null, "p_9": false, "p_15": [ 0, 12, 14, 15, 26 ], "p_16": [ 1, 3, 26 ] }, "k_user/2854029": { "_key": "2854029", "_id": "k_user/2854029", "_rev": "2857345", "_type": 0, "p_0": "d1ef8422-2e7d-491c-84ac-95d0a1647d0e", "p_1": { "fr": "Utilisateur système", "en": "System user" }, "p_10": false, "p_12": true, "p_14": false, "p_18": 2, "p_19": false, "p_20": false, "p_21": 0, "p_22": 2, "p_23": false, "p_4": "2016-10-28T16:46:37.603Z", "p_6": "2016-10-28T16:46:37.603Z", "p_8": null, "p_9": false, "p_15": [ 0, 12, 14, 15, 76, 78 ], "p_16": [ 1, 3, 78 ] } } };

res.send(w);

}) .body(joi.object({ trace: joi.boolean().default(false),
}).required(), 'Paramètres d\'exécution de la requête') .summary('Test') .description('Test'); ` When this request is called from Web ArangoDB Interface, it takes few milliseconds (5/6 ms) to return. Running with the driver, it takes half a second (500 ms). Time measurement applies only to http request by inserting Stopwatch in Connection.Send method.

I've done some tests with Restsharp and get same results. Does Arango Web Interface something special ?

Perhaps there is a specific way to setup TCP/IP connection or we need to insert custom headers in http call

Any idea?

yojimbo87 commented 7 years ago

I did some performance tests with a foxx service that returns same data as in your case with 10k iterations:

JIT: 1054,91ms. (disregard JIT pass) Optimize: 1,40ms. (disregard optimize pass) Total: 5371,07ms. (10k iterations total time) Average: 0,54ms. (average time for one call - not just HTTP request, but also driver processing)

How did you test it? Is your time taken from just one call in debug mode?

thierry-github commented 7 years ago

test have been done in a for/each loop with 500 calls. Times are taken this way :

Stopwatch c = Stopwatch.StartNew();
using (var httpResponse = (HttpWebResponse)httpRequest.GetResponse())                                    
using (var responseStream = httpResponse.GetResponseStream())
using (var reader = new StreamReader(responseStream))
{
      response.StatusCode = (int)httpResponse.StatusCode;
      response.Headers = httpResponse.Headers;
      response.Body = reader.ReadToEnd();

      reader.Close();
      responseStream.Close();
}

c.Stop();
Console.WriteLine(response.StatusCode + " - " + c.ElapsedMilliseconds);

May the issue is bound to my testing environment.

Do you use some authentication module ?

yojimbo87 commented 7 years ago

It might be bound to testing environment, but the difference is too high I would say. I'm not using authentication module.

In my performance testing code I'm using stopwatch Elapsed property and then TotalMilliseconds to count the times, rather than ElapsedMilliseconds which might return different results. Can you please try to use Elapsed.TotalMilliseconds to see what the result will be?

thierry-github commented 7 years ago

Found the problem : it was my antivirus who check http/https traffic. But for some reasons, traffic from arangodb web site was excluded, . After some settings, performance are back. It seems that Json structure provided by the service makes my antivirus nervous. Thanks for your testing

yojimbo87 commented 7 years ago

Thanks a lot for your testing too.

yojimbo87 commented 7 years ago

Merged to master branch.