Hallo, ich probiere mich immer noch an ASP.NET Core mit GraphQL und Dapper.GraphQL aus.
Beim herumspielen ist mir aufgefallen das wenn man Dapper verwenden möchte, ein Nuget existiert, dass Dapper.GraphQL heißt. Soweit so gut. Ich bin dem Beispiel gefolgt und bin dann auf einen Punkt gestoßen der bei mir für Unsicherheit sorgt.
Beim Registrieren der Schema/Types etc. in der Middleware bzw. unter Startup.cs ConfigureServices, müssen für Dapper.GraphQL die entsprechenden Types etc. über services.AddDapperGraphQL registriert werden. Das ganze scheint als Singelton zu erfolgen. Das Schema allerdings habe ich nur über services.AddScoped registriert, da ich z.B. in dem Beispiel von Abt ebenfalls die Registrierung über Scoped gesehen habe.
Scoped wird bei jedem Request erstellt, werden ein Singelton erstellt wird sobald dieser zum ersten mal gebraucht wird, und dieser wird dann die ganze Zeit über erhalten.
Hier einmal ein paar Auszüge:
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ISchema, MyQuerySchema>();
services.AddSingleton<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));
services.AddDapperGraphQL(options =>
{
// Add GraphQL types
options.AddType<UserAccountType>();
options.AddType<MyGraphQuerySchema>();
// Add query builders for dapper
options.AddQueryBuilder<UserAccount, UserAccountQueryBuilder>();
});
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
Controller
private readonly ISchema _schema;
public GraphQLController(ISchema schema)
{
_schema = schema;
}
public async Task<IActionResult> Post([FromBody] GraphQLQuery query)
{
var inputs = query.Variables.ToInputs();
ExecutionResult result = await new DocumentExecuter().ExecuteAsync(_ =>
{
_.Schema = _schema;
_.Query = query.Query;
_.OperationName = query.OperationName;
_.Inputs = inputs;
}).ConfigureAwait(false);
if(result.Errors?.Count > 0)
{
return BadRequest();
}
return Ok(result);
}
MyQuerySchema
public class MyQuerySchema : Schema
{
public MyQuerySchema(IDependencyResolver resolver):base(resolver)
{
Query = resolver.Resolve<MyGraphQuerySchema>();
}
}
Den Resolver verwende ich hier damit ich später auch mit Mutations arbeiten kann. Das habe ich so in mehreren Beispielen gesehen.
Meine Frage ist nun um es noch einmal zusammen zu fassen: Macht es Sinn in einem Scoped, Singelton zu verwenden? Denn MyQuerySchema ist ja mit einer der wichtigsten Teile über den dann mehr oder weniger die anderen Dinge laufen wie MyGraphQLQuerySchema also wo das Mapping etc. stattfindet. Von Dapper.GraphQL bin ich nägmlich gezwungen einige Teile als Singelton zu registrieren. Oder ergibt es mehr Sinn einfach alles als Singelton fest zu legen?
Beide Fälle funktionieren, dass habe ich getestet, aber es könnte ja später doch vielleicht zu Problemen führen? Ich würde gerne möglichst sauber lernen und nicht so viel fuschen.
Wenn ich etwas unklar definiert habe, bitte bescheid geben damit ich es ändern kann.
Note: Wenn jemand entsprechendes Lesematerial hat, freue ich mich natürlich ebenfalls. Die offizielle MS Doc habe ich natürlich auch schon dazu gelesen. Mich würde nur mal das zusammenspiel in so einem Fall interessieren.
Beim herumspielen ist mir aufgefallen das wenn man Dapper verwenden möchte, ein Nuget existiert, dass Dapper.GraphQL heißt. Soweit so gut. Ich bin dem Beispiel gefolgt und bin dann auf einen Punkt gestoßen der bei mir für Unsicherheit sorgt.
Hört sich nach ner ganz schlechten Idee an.
Dapper ist Datenbankschicht - GraphQL ist die Anssichtsschickt.
Allein das Konzept ist eine Verletzung der [Artikel] Drei-Schichten-Architektur
Auch die Grundidee, dass GraphQL aus mehreren Quellen Daten konsolidiert, um die Anfragemenge zu minimieren, würde DapperGraphQL völlig unterlaufen.
Macht es Sinn in einem Scoped, Singelton zu verwenden?
Aus einem Scoped darf man einen Singleton verwenden, aber nicht umgekehrt.
Ansonsten wird der Scoped automatisch zu einer Singleton-Referenz (im Singleton selbst).
Das kann zu Race Conditions führen.
Oder ergibt es mehr Sinn einfach alles als Singelton fest zu legen?
Datenbank(verbindungen) niemals* als Singleton definieren.
Gibt Ausnahmen, die man an einer Hand abzählen kann. Hier sehe ich keine.
Beide Fälle funktionieren, dass habe ich getestet, aber es könnte ja später doch vielleicht zu Problemen führen?
Das Konzept Datenbank Entitäten in den API Return zu pressen ist ein Fehler, korrekt 😃
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Hört sich nach ner ganz schlechten Idee an.
Dapper ist Datenbankschicht - GraphQL ist die Anssichtsschickt.
Allein das Konzept ist eine Verletzung der
>
Aber mit EntityFramework würde es doch auf das selbe hinauslaufen oder? Dapper.GraphQL bringt nur einen SQL Builder und einen Mapper mit, mit dem dann aus der GraphQL Query ein SQL erstellt wird.
Vielleicht verstehe ich dich auch falsch, aber an der Stelle wo Daten nun abgefragt werden, also wie in deinem Beispiel bei MyGraphQLQuerySchema, sah ich Beispiele mit EF bei denen an dieser Stelle nun Daten abgefragt werden, oder man verwendet Repositorys oder wie du ein Mediater. In dem Fall wollte ich einfach anstatt EF, Dapper verwenden. Dazu gibt es dann das Nuget Dapper.GraphQL womit aus der GraphQL Query ein SQL erstellt wird. Und damit kann ich halt meine Daten holen.
Weil man muss ja irgendwie aus der Query ein gültiges SQL erstellen.
Oder meinst du das, dass Abfragen der Daten in dieser Konstellation nicht schön ist? Sonder das man z.B. ein Mediater etc. an dieser Stelle verwenden sollte der einem bei der Datenbeschaffung hilft?
Ich wollte das mit dem Mediater noch ausprobieren aber das ganze erst so simple wie möglich halten damit ich nicht so durcheinander komme 😄
Quasi wie in disem "Ofiziellen" Beispiel:
Field<ListGraphType<PersonType>>(
"people",
description: "A list of people.",
resolve: context =>
{
// Create an alias for the 'Person' table.
var alias = "person";
// Add the 'Person' table to the FROM clause in SQL
var query = SqlBuilder.From($"Person {alias}");
// Build the query, using the GraphQL query and SQL table alias.
query = personQueryBuilder.Build(query, context.FieldAst, alias);
// Create a mapper that understands how to map the 'Person' class.
var personMapper = new PersonEntityMapper();
// Open a connection to the database
using (var connection = serviceProvider.GetRequiredService<IDbConnection>())
{
// Execute the query with the person mapper
var results = query.Execute(connection, personMapper, context.FieldAst);
// `results` contains a list of people.
return results;
}
}
);
Source: https://github.com/landmarkhw/Dapper.GraphQL/blob/master/README.md
Das Konzept Datenbank Entitäten in den API Return zu pressen ist ein Fehler, korrekt 😃
Wie meinst du das, ich kann dir da gerade nicht ganz folgen.
Aber mit EntityFramework würde es doch auf das selbe hinauslaufen oder?
Dapper.GraphQL bringt nur einen SQL Builder und einen Mapper mit, mit dem dann aus der GraphQL Query ein SQL erstellt wird.
Richtig. Genauso kacke.
Man bindet keine Datenbankschicht - egal über welchen Provider - direkt an die API.
Damit untergräbst Du - egal mit welchem Provider - völlig das Konzept der Schichtentrennung.
Oder meinst du das, dass Abfragen der Daten in dieser Konstellation nicht schön ist? Sonder das man z.B. ein Mediater etc. an dieser Stelle verwenden sollte der einem bei der Datenbeschaffung hilft?
Der Mediator ist die Schnittstelle der Logik.
Der Handler eines Mediators beinhaltet vollständig die Logik der Anwendung (bzw. die Menge an Handlern).
Quasi wie in disem "Ofiziellen" Beispiel:
Field<ListGraphType<PersonType>>( "people", description: "A list of people.", resolve: context => { // Create an alias for the 'Person' table. var alias = "person"; // Add the 'Person' table to the FROM clause in SQL var query = SqlBuilder.From($"Person {alias}"); // Build the query, using the GraphQL query and SQL table alias. query = personQueryBuilder.Build(query, context.FieldAst, alias); // Create a mapper that understands how to map the 'Person' class. var personMapper = new PersonEntityMapper(); // Open a connection to the database using (var connection = serviceProvider.GetRequiredService<IDbConnection>()) { // Execute the query with the person mapper var results = query.Execute(connection, personMapper, context.FieldAst); // `results` contains a list of people. return results; } } );
Source:
>
Und das ist halt genau das was ich meine: Zum Teufel gehört kein SQL Code in das Schema.
Auch sieht man hier Service Locator Anti-Pattern.
Ganz klassischer Fail der [Artikel] Drei-Schichten-Architektur
Das Schema ist nur der Relay zur Logik.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code
Achso okey. wir sind zwar ein wenig vom Thema abgekommen aber ich fasse noch einmal zusammen:
Die Beispiele die unter anderem dargestellt wurde auf Github etc. sind nicht sauber, da in der Präsentationsschicht sofort Logik und Datenbankzugriff mit drin ist.
Besser wäre es mit z.B. einem Mediater zu arbeiten in Verbindung mit vielleicht dem Repository Pattern und mit diesen dann die Logik und den Datenbankzugriff zu steuern.
Somit müsste der SQLBuilder etc. der von Dapper.GraphQL mitgebracht wird, mit in den Mediator rutschen, bzw. halt aus dem Schema fliegen.
Ein Singelten kann in ruhe in einem Scoped verwendet werden.
Setze ich diese Punkte so um, habe ich eine (hoffentlich) präsentable Lösung für mein Problem.
Jo, Schema ruft Logik (zB. Mediator) auf und der Handler spricht mit der Datenbank.
- performance is a feature -
Microsoft MVP - @Website - @AzureStuttgart - github.com/BenjaminAbt - Sustainable Code