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

Lack of Model inheritance effectively prevents some shared Vapor packages within an Organization #46

Closed
lukeify opened this issue Jun 22, 2019 · 4 comments
Labels
question Further information is requested
Projects

Comments

@lukeify
Copy link

lukeify commented Jun 22, 2019

Firstly, I apologise if this issue is located incorrectly. Many references point to using fluent as the location to log similar issues, but I could also see this reasonably placed in vapor or fluent-kit.

I have a business need to have two separate applications that share similar authentication & user modelling within an organisation. Business-specific JWT authentication mechanisms with custom properties, business-specific middleware instances to parse & verify these JWTs, business-specific database models, etc.

Given my experience with C# & Laravel, my first assumption was that I could abstract most of this to a base package and import it via Package.swift, similarly to how users develop Vapor applications by importing Vapor. However, I found this approach simply does not work with Vapor due to a number of competing restrictions which effectively prevent businesses from sharing and deduplicating code; and from a high-level view, it feels like this should be improved.

One part of this base logic I wanted to abstract was a series of Fluent models that are effectively shared across these two separate applications. Let’s call them CustomUser, CustomRole, and CustomOrganization. My hope was that I could effectively declare these models in my base package, and then use them in both specific apps and extend them as appropriate. However, I ran into my main issue: the use of the protocol Model currently requires that conforming classes implement the final keyword. This prevents any subclassing at all. Without the final keyword, I receive many variations on the same error.

Protocol x requirement y cannot be satisfied by a non-final class

Screen Shot 2019-06-23 at 9 18 30 AM

That’s okay. I’ll just remove Model conformance in my base package. But I can’t do this either, because now representing Parent<A, B> or Child<A, B> relations don’t work. I also can’t use composition via extensions because Swift does not (really) allow properties to be declared in extensions. Even if I declared a computed { get set } property, I’m not sure Fluent would be happy with that anyway.

So this really leaves me declaring some mediocre protocols that meet the basic properties of each base model, with each application having to re-implement duplicated functionality. From a 10,000ft perspective—this doesn’t feel very DRY or like a good use of time. In other frameworks, this is a trivial task. Is this really the best that can be done?

Screen Shot 2019-06-23 at 9 19 51 AM

Am I missing something that would otherwise allow me to accomplish what I’m looking for? Because if there isn’t, I feel like this is a good target to improve in future iterations of Vapor/Fluent.

@tanner0101 tanner0101 transferred this issue from vapor/fluent Jun 24, 2019
@tanner0101 tanner0101 added the question Further information is requested label Jun 24, 2019
@tanner0101 tanner0101 added this to To Do in Vapor 4 via automation Jun 24, 2019
@tanner0101
Copy link
Member

tanner0101 commented Jun 24, 2019

I've transferred this to FluentKit since that's where Fluent's core APIs are located (including Model).

Vapor (and all of its packages) try to avoid inheritance like the plague. Mainly because it really doesn't mix well with protocols (as can be seen in your example). Given that protocols are generally more powerful than inheritance (they are composable), we just use protocols.

I'd be interested to see more concrete examples of what you are trying to do here with sub-classing and what difficulties you are having making it into a protocol.

@fwgreen
Copy link
Contributor

fwgreen commented Jul 29, 2019

Having inheritance strategies like Single Table Inheritance and others would be handy to people coming from JPA based ORMs like Hibernate. There are cases when only the behavior of the entities differ and nothing else:

class Nurse {
    var employeeID: String
    var firstName: String
    var lastName: String
    //...
}

class RegisteredNurse : Nurse {}

class PracticalNurse : Nurse {}

/*
There are tasks that can be performed by all nurses, but 
some are restricted to Registered Nurses.  
*/

This is straightforward in JPA, but I don't see how to model it using Protocols (maybe Swift 7 will have abstract classes 😄 ).

@tanner0101
Copy link
Member

This is one way you could implement that design w/ protocols:

protocol Nurse {
    var employeeID: String { get set }
    var firstName: String { get set }
    var lastName: String { get set }
    //...
}

final class RegisteredNurse : Nurse {
    var employeeID: String
    var firstName: String
    var lastName: String
}

final class PracticalNurse : Nurse {
    var employeeID: String
    var firstName: String
    var lastName: String
}

Vapor 4 automation moved this from Backlog to Done May 8, 2020
@fwgreen
Copy link
Contributor

fwgreen commented May 8, 2020

@tanner0101 Wouldn't the ORM need a discriminator column to tell which form of Nurse you are working with? Is there a complete example of this somewhere?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
Vapor 4
  
Done
Development

No branches or pull requests

3 participants