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

data fetching in fluent #37

Closed
Evertt opened this issue May 16, 2016 · 3 comments
Closed

data fetching in fluent #37

Evertt opened this issue May 16, 2016 · 3 comments
Assignees
Labels
enhancement New feature or request

Comments

@Evertt
Copy link
Contributor

Evertt commented May 16, 2016

Hey guys, when I was working on the Query object, I was using the Laravel Query Builder as an inspiration to make a nice and readable API.

And a few hours ago I was reading the MongoKitten docs and felt very inspired by its Cursor object, which you can iterate over to get documents from it and secretly behind the scenes it fetches the results in batches.

And now I'm thinking, wat is the best way to do all this? I would like to build Fluent in such a way that it encourages developers to design their apps well, with SOLID principles in mind.

That's why I personally prefer data-mapper-ORMs over active-record-ORMs, because they encourage developers to better separate concerns in their apps.

Anyhow, so I would like to have a discussion about designing Fluent. For example, do we want to have an EntityCollection class which holds an array of entities and has some convenience methods to again filter them down? And that then the Query returns entity-collections instead of arrays? Do we also want some Iterator that retrieves the results in batches like the Cursor object in MongoKitten?

Etcetera... I honestly have no idea of all the different possible ways to do this, but I want to research it. And I'll happily make a proposal myself, but right now I have visitors so I'll come back to this later. In the meanwhile I'd love to hear your thoughts about this.

@tanner0101 tanner0101 changed the title [Discussion] Design of Fluent data fetching in fluent May 16, 2016
@tanner0101
Copy link
Member

@Joannis mentioned bringing the cursor to Fluent. I think that would be a great idea. I'm going to start working on it later today and getting some of the basics set up with Mongo and SQLite. I will be able to better answer this question once I get back into it.

@Evertt
Copy link
Contributor Author

Evertt commented May 17, 2016

Anther thing we need to think about is relationships between models. How to find out which models are related to which, how to make the syntax as readable and intuitive as possible and how do we retrieve the related entities.

At first I was thinking that we could probably manage that by just using reflection on any entity and checking if any of its properties is another entity or an array of other entities. And yes I've tried it and that does work to discover what kind of relationships an entity has.

BUT, the problem is that we want to make relationships load lazily. So I thought about it some more and these are the things I can think of:

We could create a Relationship protocol and their implementations OneToOne<T: Entity, U: Entity>, OneToMany<T: Entity, U: Entity>, ManyToOne<T: Entity, U: Entity>, ManyToMany<T: Entity, U: Entity>. Or something similar to that. And then define entities like so:

class Post: Entity {
    var id: String?
    var title: String
    var body: String
    lazy var user: ManyToOne<Post, User> = ManyToOne(self, key: "user_id")

    required init(unboxer: Unboxer) {
        id    = unboxer.unbox("id")
        title = unboxer.unbox("title")
        body  = unboxer.unbox("body")
    }
}

class User: Entity {
    var id: String?
    var name: String
    lazy var posts: OneToMany<User, Post> = OneToMany(self, reverseKey: "user_id")

    required init(unboxer: Unboxer) {
        id    = unboxer.unbox("id")
        name  = unboxer.unbox("name")
    }
}

Although I don't find this the prettiest solution. Another solution I could think of, which results in better syntax, but also goes back to a more Active-Record style is the following:

// Entity.swift
extension Entity {
    func belongsTo<T: Entity>(_ entityType: T.Type) -> T {
        let key = String(entityType) // let's just use convention for determining the key
        return Query(entityType).someQueryToRetrieveRelatedEntity()
    }
}

// Post.swift
class Post: Entity {
    var id: String?
    var title: String
    var body: String
    lazy var user: User = self.belongsTo(User)

    required init(unboxer: Unboxer) {
        id    = unboxer.unbox("id")
        title = unboxer.unbox("title")
        body  = unboxer.unbox("body")
    }
}

That second one ends up in prettier code in Post, but it seems to mess up the separation of concern. Any thoughts?

Edit

Oh and now I just realized we'd also need to support eager loading and I just did a test and this works:

class Post: Entity {
    var id: String?
    var title: String
    var body: String
    lazy var user: User = self.belongsTo(User)

    required init(unboxer: Unboxer) {
        id    = unboxer.unbox("id")
        title = unboxer.unbox("title")
        body  = unboxer.unbox("body")

        if let user: User = unboxer.unbox("user") {
            self.user = user
            // Because we initialized user here, the belongsTo() method is not executed.
            // Which means we can support eager loading and avoid the n+1 problem! :-D
        }
    }
}

@tanner0101 tanner0101 added enhancement New feature or request reviewing and removed discussing labels May 18, 2016
@tanner0101
Copy link
Member

Mapping was added with Node.

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

No branches or pull requests

2 participants