User backend API overhaul

it is a is a relationship. Every ICreateUserBackend is also a IUserBackend: Is-a - Wikipedia

I will have to disagree. IMHO ICreateUserBackend is an interface that models behaviour of a class. It is a capability. A capability can not inherit from an “object” (not in OO sense).

The classic example would be to have an abstract class (or interface) Vehicle and then IFly, IDrive and ISwim. A car would inherit from Vehicle and implement IDrive, for example. Aren’t you proposing to let IDrive inherit from Vehicle?

Part of the problem IMHO also stems from the name of the interfaces. It should be something that hints at a capability not an object, so maybe ICanCreateUser (there are probably better choices) would be more appropriate. What do you think?

IMO that is a bit too much. It’s obvious that the call has to ask this every time, like per each request. And the the result must not be cached.

I think code can never be clear enough, personally. All the things that you mention are not clear to me when I read the methods name. currently implies that capabilities are not constant over time (although over configurations would be more appropriate) and immediately hints that the returned value might change any time and thus it must be called frequently.

To quote Robert.C.Martin, the author of Clean Code:

Don’t be afraid to make a name long. A long descriptive name is better than a short enigmatic name. A long descriptive name is better than a long descriptive comment. Use a naming convention that allows multiple words to be easily read in the function names, and then make use of those multiple words to give the function a name that says what it does.

The classic example would be to have an abstract class (or interface) Vehicle and then IFly, IDrive and ISwim. A car would inherit from Vehicle and implement IDrive, for example. Aren’t you proposing to let IDrive inherit from Vehicle?

I am. The reason being is that if you later have a function

function foo(ICreateUserBackend $be): void {

}

you can only use methods of the specific ICreateUserBackend but no the IUserBackEnd in general with your approach. Like you can create a user but you can’t get the name of the back-end argument. And I see really no case where we would need a ICreateUserBackend except for the user back-ends. ICreateUserBackend is a specialization of the user back-end interface. So the comparison with the generic IDrive is inappropriate IMO.

If you use the ICreateUserBackend as the type for your method’s argument you only care about the action of creating a user. Isn’t this the whole purpose of small interfaces that you select whichever function you are interested in? Why would you want to know a name then? If your method is interested in that you can use IUserBackEnd. foo() seems like an artificial method that would not exist in reality and IMHO YAGNI. Do you have a concrete example of what foo() would be?

If you use the ICreateUserBackend as the type for your method’s argument you only care about the action of creating a user.

This is an assumption.

I want to design a flexible and future proof API. YAGNI does not apply. In fact I want to be able to do things that I don’t need right now.

Please understand that we’re designing something that can’t be easily changed after it’s released. This is not an internal class where you adjust the calling side to the new argument and it’s done. It takes ages to get proper changes done. And we have to avoid another generation of user interface API in two years.

Maybe our disagreement stems from a misunderstanding on how these interfaces will be used.

IMO you always implement the base IUserBackEnd and then additionally whichever action you want to support.

A concrete class could look like this:

class MyUserBackEnd implements IUserBackEnd, ICheckPasswordBackEnd, IGetEMailAddressBackend {
....
}

it implements the base interface and two action interfaces. Do you agree?

If you do, then you have “included” getBackendName() three times now. Since each of the action interfaces also inherits from IUserBackEnd. This is the problem i have with this.

I do.

If you do, then you have “included” getBackendName() three times now.

Three times the identical method if you want to see it like that. But effectively it’s only there once.

Do you possibly program in another language like C#? Then I could see your concerns as that language indeed treats the interfaces slightly different and you could have separate implementations for all three. But in php those result in no overlap, duplication, conflict or whatsoever. It’s the single same method getBackendName that MyUserBackEnd has to implement.

Again, the way I see it is that the specialized interfaces are is-a to the back-end interface. They are a subtype, if we wanna call it that.

Again, the way I see it is that the specialized interfaces are is-a to the back-end interface. They are a subtype, if we wanna call it that.

I think it boils down to this: Do you think the following is a valid use of the new API?

class MyUserBackEnd2 implements ICheckPasswordBackEnd {
....
}

so without the base interface?

Yes, it is.

1 Like

OK, we got this out the way then. :slight_smile:

I disagree and think this should not be allowed. I would favor composition over inheritance in this case.

Anyway, i am working on a pull request of my own. I think, we will be able to discuss this better once we have two drafts to compare directly.

We’re duplicating work. This is not a competition.

Let’s put this on hold until others got time to read up and tell us their preferences.

At least Psalm would be happy with the type design you propose: https://psalm.dev/r/3cdd3527eb

Okay this one shows it a bit better, I think: https://psalm.dev/r/314e83ea31

So in bar a B can also be an A if you explicitly check. So from a type system PoV this is sound.

But semantically it’s a bit strange for me still. In this code I want to have a specialized version of a user interface. But I can’t use the base user backed interface unless I explicitly check the type.

Also with the non-extended design one can implement a class that has ICreateUserBackEnd for example but not IUserBackEnd – would that even make sense?

Like I would totally support your reasoning if the ICreateUserBackEnd or similar was to be reused for something else. But the point is that this is for user back-ends and user back-ends only. I can’t stress this enough that it’s a prime example of a subtype.

And as much as I preach using composition otherwise, it is not necessarily the same when it comes to classes vs interfaces.

I think I get where @PancakeConnaisseur is coming from and it seems weird to have multiple interfaces, that should be used together, inherit from the same root interface. They seem more like mixins to IUserBackEnd than proper interfaces. Still, they are proper interfaces and could be used individually, so i think @ChristophWurst has a point that it wouldn’t make sense to use ICreateUserBackEnd individually, if it doesn’t inherit from IUserBackend.

1 Like

I agree, but there are various things that I would want to do differently and it is much easier to just show them in code than try to explain every little difference using this forum or comments on Github. Additionally, actually writing the code helps me understand what will work and what will not. There are other considerations that I haven’t written down, yet that came up while I was developing the PR.

IMHO interfaces are about functions only, they are contracts that guarantee functionality. You seem to see them more as abstract classes, because you expect them to be “enough” to create an actual class when you inherit from them. I would never expect to able to get a usable class just from inheriting an interface. For me, this is what abstract classes are for, not interfaces.

While writing the previous paragraph, I realized: We could change IUserBackEnd to UserBackEndAbstract and make it an abstract class. This would hint to future developers that this is something of a basis for user back ends that you must inherit and that then you can additionally and optionally implement any of the the action interfaces.

@ChristophWurst I created the pull request. Looking forward to your and other people’s feedback