Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SQL / ORM-Like bindings extension #3647

Open
salaboy opened this issue Jan 13, 2025 · 7 comments · May be fixed by #3660
Open

SQL / ORM-Like bindings extension #3647

salaboy opened this issue Jan 13, 2025 · 7 comments · May be fixed by #3660

Comments

@salaboy
Copy link

salaboy commented Jan 13, 2025

Describe the proposal

Currently, Bindings provides a very generic way to access SQL databases. While this is a great generic mechanism, bindings will benefit from an interface offering more functionality out-of-the-box, following common patterns that ORM frameworks implement without becoming a full ORM implementation. Always keeping simplicity in mind and making sure that developers can consume these behaviors using REST/RPC APIs (language agnostic). I've updated the proposal to use the concept of Entity thanks to @marcduiker for the dotnet reference.

  • registerEntity (entityName, json): register a new json object type, this is a map of fieldName + type, each field maps to a column in a SQL table
  • save ( entityName , json) : create an insert SQL clause mapping the fields from the struct to the specified type, done by fieldName. (Alternatively add used in dotnet)
  • findById ( entityName, id ): creates a SQL select query using the type for the table name and filters by the Id specified.
  • existById ( entityName, id ): creates a SQL select query using the type for the table name and filters by the Id specified, returns true or false, if a row is returned.
  • findAll ( entityName ): creates a SQL select query using the type for the table name, returns a collection of json objects
  • count ( entityName ): creates a SQL select query using the type for the table name, returns an integer with the number of rows returned
  • deleteById ( entityName, id ): creates a SQL delete query using the type for the table name and filter by Id to delete a single row (alternatively remove used in dotnet)
  • deleteAll ( entityName ): creates a SQL delete query using the type for the table name, delete all rows

Notice that these should be APIs exposed by the Binding. Instead of creating a new API / Building Block, I suggest extending the current binding functionality following this functionality. This can then be promoted to its own building block—the main reason why this is recommended as part of the Bindings API is that all ORM frameworks always provide the "Query" escape hatch, which is the core of what Bindings provides nowadays.

This proposal aims to provide a much more straightforward approach than frameworks like GORM -> https://gorm.io/docs/, which also provides a full-blown programming model.

Examples will follow in a separate comment.

@salaboy
Copy link
Author

salaboy commented Jan 13, 2025

Let's imagine that we have the following SQL Table, from https://www.w3schools.com/postgresql/postgresql_create_demodatabase.php

CREATE TABLE customers (
  customer_id SERIAL NOT NULL PRIMARY KEY,
  customer_name VARCHAR(255),
  contact_name VARCHAR(255),
  address VARCHAR(255),
  city VARCHAR(255),
  postal_code VARCHAR(255),
  country VARCHAR(255)
);

This will require to call the following APIs:

First, let's register a new Entity:

registerEntity("customers", ["customer_id", "customer_name", "contact_name", "address", "city", "postal_code", "country" ]) -> this is to keep it simple without any type information.

Once the entity is registered we can use findAll

findAll("customers")` -> returns an array of columns, or if we can map this to a struct that would be quite awesome.

This results in a simple query:

select "customer_id", "customer_name", "contact_name", "address", "city", "postal_code",  "country" from customers

A similar query will be created to filter by ID.

findById("customers", id)

This results in a simple query: select "customer_id", "customer_name", "contact_name", "address", "city", "postal_code", "country" from customers where customer_id= ?

The mechanism will be the same for delete functions.

If we want to save a new customer:

save("customers", &customer) 

This results in the following query:

INSERT INTO customers (customer_name, contact_name, address, city, postal_code, country)
VALUES (  <values from &customer fields> );

To keep it simple, we should return the errors generated from the specific database to the user for troubleshooting. This API shouldn't do any advanced type mapping.

The implementation of this proposal will require each binding to have a reduced collection of parameterizable SQL statements that will be used to perform the operations described in the APIs, all following the same conventions.

@salaboy salaboy changed the title SQL / ORM Like bindings extension SQL / ORM-Like bindings extension Jan 13, 2025
@salaboy
Copy link
Author

salaboy commented Jan 17, 2025

For now I've taken inspiration in the CrudRepository interface from Spring Data -> https://github.com/spring-projects/spring-data-commons/blob/main/src/main/java/org/springframework/data/repository/CrudRepository.java but I will be happy to align with dotNet ORM framework semanticts too @marcduiker can you. help me out with a reference here?

@marcduiker
Copy link
Contributor

For now I've taken inspiration in the CrudRepository interface from Spring Data -> https://github.com/spring-projects/spring-data-commons/blob/main/src/main/java/org/springframework/data/repository/CrudRepository.java but I will be happy to align with dotNet ORM framework semanticts too @marcduiker can you. help me out with a reference here?

The most well-known ORM in .NET is Entity Framework. It uses a DbContext and DbSets and has similar methods as you mentioned.

@marcduiker
Copy link
Contributor

Can you explain the scope a bit more @salaboy? Is this proposal to extend the current Bindings API or to only extend the individual Dapr SDKs, which in turn would translate the 'repository' methods to use the existing Bindings API?

@salaboy
Copy link
Author

salaboy commented Feb 1, 2025

The idea is to keep it super simple, for now the only change proposed here is to add more "actions" to existing SQL bindings.

This allows us to have a set of well-defined behaviors for SQL bindings beyond "query" and "exec". The new actions suggested in the issue description align with the idea of Entities, meaning 1 entity <-> 1 table. So for example: findAll("customers") is an action that acts on the customers table, this also introduce the requirement or registering (or creating) tables that has columns with different datatypes, hence I've introduced the registerType that now I can confidently rename to registerEntity.

@salaboy
Copy link
Author

salaboy commented Feb 1, 2025

To clarify, this doesn't require any changes in the SDK, the proposal is now just adding new "actions" to the existing bindings. This is an opt-in feature from the perspective of the user. This is also a backward compatible change as actions such as "query" and "exec" will remain unmodified.

@salaboy salaboy linked a pull request Feb 1, 2025 that will close this issue
3 tasks
@salaboy
Copy link
Author

salaboy commented Feb 1, 2025

@marcduiker I've added a PR with the in-progress PoC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants