Skip to content

Latest commit

 

History

History
247 lines (182 loc) · 9.17 KB

014-response-model.md

File metadata and controls

247 lines (182 loc) · 9.17 KB

Response Model in SDKs for improved DX

  • Implementation Owner: @lohanidamodar
  • Start Date: 02-25-2021
  • Target Date: N/A
  • Appwrite Issue: N/A

Summary

Having proper response model while making an API request would improve developer experience a lot. This will make working with Appwrite SDKs a lot easier.

Problem Statement (Step 1)

What problem are you trying to solve?

At the moment, all of the SDKs return JSON response which then have to be parsed by the developers to make proper use. Instead, having a proper response model and returning response object instead of JSON would make developers tasks a lot easier. They will not have to make their own response model for each and every endpoints, they will get proper response objects that they can use natively.

What is the context or background in which this problem exists?

All of Appwrite's endpoints return JSON objects, which can be parsed into native models and accessed. However

Once the proposal is implemented, how will the system change?

The Appwrite itself already provides the Response object in the Swagger specification. So it will not change. However, All the SDKs will change, each functions will return proper response object.

Design proposal (Step 2)

SDK templates should now should also consider the response object and response schema defined in the Swagger specification. We need to get those schemas and response definitions from Swagger to SDK templates and perpare a proper response model.

1. Define models

Define model of all available response objects. From Swagger spec, we can get the list of all the available response objects in definitions object where each response object is key=>value paired where key is the name of the response object and value the definition of all the fields. For each of these response object, in each SDK we should create models.

2. Return object instead of JSON

Once we have all the models, for each API end points based on the response definitions (available in responses object under schema) in the Swagger, we should convert the JSON to corresponding object and return.

For each SDK the process of converting JSON to respective model might be different, so we should work accordingly.

Below we will see an example from Dart/Flutter SDK. Let's look at create user endpoint. According to Swagger definitions, it returns User on successful request, so first we create user model as follows.

class User {
    User({
        this.id,
        this.name,
        this.registration,
        this.status,
        this.email,
        this.emailVerification,
        this.prefs,
    });

    String id;
    String name;
    int registration;
    int status;
    String email;
    bool emailVerification;
    String prefs;

    factory User.fromJson(Map<String, dynamic> json) => User(
        id: json["\$id"],
        name: json["name"],
        registration: json["registration"],
        status: json["status"],
        email: json["email"],
        emailVerification: json["emailVerification"],
        prefs: json["prefs"],
    );

    Map<String, dynamic> toJson() => {
        "\$id": id,
        "name": name,
        "registration": registration,
        "status": status,
        "email": email,
        "emailVerification": emailVerification,
        "prefs": prefs,
    };
}

So now the create user function,

Future<User> create({@required String email, @required String password, String name = ''}) async {
    final String path = '/users';

    final Map<String, dynamic> params = {
        'email': email,
        'password': password,
        'name': name,
    };

    final Map<String, String> headers = {
        'content-type': 'application/json',
    };

    final res = await client.call(HttpMethod.post, path: path, params: params, headers: headers);
    return User.fromJson(res.data);
}

For things loke Documents, Permissions we create base Models. For example

abstract class Document {
    String id;
}

abstract class Permissions {
    List<String> read;
    List<String> write;
}

Examples on other SDKs

Kotlin

data class UserList(
    val users: List<User>,
    val sum: Int
)

data class User(
    @SerializedName("\$id")
    val id: String,
    val name: String,
    val registration: Int,
    val status: Int,
    val passwordUpdate: Int,
    val email: String,
    val emailVerification: Boolean,
    val prefs: Preferences
)

data class Preferences(
    val data: MutableMap<String, Any>
)

class Test {
    fun main(context: Context) {
        val client = Client(context)
        val account = Account(client)
        val users = Users(client)

        GlobalScope.launch {
            // User
            val accountResponse: Response = account.get()
            val accountBody: String = accountResponse.body!!.string()
            val user: User = accountBody.jsonCast<User>()
            
            // UserList
            val usersResponse: Response = users.list()
            val usersBody: String = usersResponse.body!!.string()
            val userList: UserList = usersBody.jsonCast<UserList>()
        }
    }
}

Prior art

May popular SDKs for popular softwares, always return proper response objects instead of plain String or JSON. This improves developer experience a lot. A developer can easily understand, what object the API is returning, what methods and properties are available.

Unresolved questions

  1. Handling serialization and dserialization in each SDKs by keeping things similar between SDKs as well as keeping the native feel of the SDKs as it is.
  2. Properly displaying the response Objects for each SDKs on the Docs
  3. For base models, whether to use interface or abstract class. Interface would force user to add Appwrite specific properties.
  4. Do we create a Base response model that we return from every methods instead of returning the object itself. For example the user create method above instead of returning Future<User> would return Future<Response<User>>. This will make it similar accross services.
  5. What do we do with conflicting model names as Locale model and Locale service would conflict. Do we define all models as AppwriteLocale, AppwriteUser. Which I think is better approach. In Flutter it was conflicting as all is defined in the same library. We could try to separate the namespace. But would it be possible for every SDK and would it be simple enough?

Future possibilities