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 · 9 comments · May be fixed by #3660
Open

SQL / ORM-Like bindings extension #3647

salaboy opened this issue Jan 13, 2025 · 9 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

@berndverst
Copy link
Member

@salaboy @marcduiker IMO this does not belong into binding and should be a separate building block.

Whenever we expand bindings something is extremely wrong / flawed in the design of Dapr.

Adding yet another one off is an ugly quick fix -- there is nothing that requires any two bindings to have the same functionality or standard. And introducing such a concept would be wrong.

What is being asked here needs to be a separate building block. Adding this to bindings is not the way to go.

@salaboy
Copy link
Author

salaboy commented Feb 13, 2025

Hi @berndverst can you share more context? "Whenever we expand bindings something is extremely wrong / flawed in the design of Dapr." -> can you explain the reasons why? I am not as senior in Dapr as some of the folks here, so I would love to know why that is the case.

Even if this doesn't belong here, extending a binding to have more functionality can help us show an intermediate step towards the final solution. When looking at how frameworks solve this problem, I realized that:

  1. The client/driver needs to be externalized to the sidecar
  2. The connection pool handling can be externalized to the sidecar
  3. A clear interface of operations to deal with Entities needs to be provided.

Right now, Bindings are solving points 1 and 2. With this proposal, we are testing point 3, which can help us figure out the correct API to create a new building block.
I totally understand that not all bindings will have these operations, but we can even test how difficult is to implement these operations across all SQL bindings to find gaps and further refine the requirements for a new building block.

Your thoughts are highly appreciated.

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.

3 participants