Skip to content

Conversation

@antonkomarev
Copy link
Contributor

@antonkomarev antonkomarev commented Jan 9, 2025

Overview

Simplify registration of server, because PATH_PREFIX constant declares:

Should be used with caution, it only matches routes with the default "/twirp" prefix and default CamelCase service and method names. Use EmployeeServiceServer::getPathPrefix instead.

What this PR does / why we need it

Instead of this:

$this->router->registerHandler(
    EmployeeServiceServer::PATH_PREFIX,
    new EmployeeServiceServer(svc: $this->employeeService),
);

Or this:

$employeeServiceServer = new EmployeeServiceServer(
    svc: $this->employeeService,
);

$this->router->registerHandler(
    $employeeServiceServer->getPathPrefix(),
    $employeeServiceServer,
);

We can do:

$this->router->registerServer(
    new EmployeeServiceServer(
        svc: $this->employeeService,
    )
);

Special notes for your reviewer

New ServerInterface may have new method getMethodPath described here:

Does this PR introduce a user-facing change?

New ability to register server without manually define server path.

@antonkomarev antonkomarev marked this pull request as draft January 9, 2025 11:00
@antonkomarev
Copy link
Contributor Author

antonkomarev commented Jan 9, 2025

@sagikazarmark marked as Draft because no tests included and documentation should be updated too. If this proposal is applicable, will invest more time for it.

* Generated from protobuf service <code>{{ .Service.Desc.FullName }}</code>
*/
final class {{ .Service | phpServiceName .File }}Server implements RequestHandlerInterface
final class {{ .Service | phpServiceName .File }}Server implements ServerInterface
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swapped it to the new interface, because it is extending RequestHandlerInterface

@sagikazarmark
Copy link
Member

Thanks @antonkomarev !

I think this makes sense, although in the past I avoided adding additional dependency on the shared library on purpose, mostly to avoid incompatibility between the generated code and the shared library.

Let me sleep on it before accepting.

@sagikazarmark
Copy link
Member

Looks like I described that exact same solution in #89 though, but never got to implementing it...probably because it meant adding an interface to the runtime library.

@antonkomarev
Copy link
Contributor Author

antonkomarev commented Jan 13, 2025

@sagikazarmark so, you didn't changed your mind from then? )

I think it's redundant to define prefix manually if we already have it in a service object.

@sagikazarmark
Copy link
Member

I haven't really given it much thought. I can see how the current UX is not ideal. I'm leaning towards accepting this, I just want to give it some thought to see if we can find an alternate solution.

@antonkomarev
Copy link
Contributor Author

antonkomarev commented Jan 19, 2025

@sagikazarmark what's wrong with the external interface? Code is generated and doesn't mean to be committed to the application repository. Maybe only in tests.

@sagikazarmark
Copy link
Member

Coming back to this after @SpencerMalone mentioned an interface

Frankly, I'm not sure it's wrong per se. I just became a Go developer and got used to implicit interface implementation. 😄

I tried to follow the principles of the original Twirp library: no or minimal dependencies on a shared library. One of the biggest criticism of protobuf/grpc is that their famous backward compatibility promise goes out the window when they break the shared library.

I really am undecided on this, so if this is something that you (the community) think would be useful, I'm inclined to get this merged.

@SpencerMalone
Copy link

SpencerMalone commented Oct 28, 2025

(I also dramatically prefer the implicit interfacing in go, I wish it existed in php because of the rest of this comment, also just preface that this is all just opinions I think so feel free to ignore).

I'll say my real world use case on why there should be an interface with getPrefix:

So I have a modular monolith central router that routes to various types of servers. Some are twirphp, some are other frameworks. There's 100+ protobufs generating twirphp services. We provide some abstract classes which simplify the twirphp registration so that people can just extend off of the class generally, specify their interface, implementation, and the middleware, but the call to register is done in the abstract class. Unfortunately, there isn't a shared type across all implementations that "works" as is, so we do a method_exists, which makes me :(

My opinion though, having thought about it, is twirphp should NOT have a "central" interface like ServerInterface, as introducing new functions to an existing interface for generated classes is a breaking change IMO:

Instead, we should default to the thinnest interfaces we can that each implement a desired feature set. This is because at 100+ services, when we update twirphp, we often aren't regenerating every service at once. That is a rather frightening scope with how many services we have, instead we tend to slow roll them once we upgrade. But, if there was a central ServerInterface, and after this PR we added a new function to it in a future release, you would be forced to regenerate all of those services in the moment that you updated twirphp, which is going to be very painful (otherwise they would not correct implement ServerInterface and throw an error). For that reason, I recommend that we do very very thin interfaces like hasPathPrefix and avoid a larger central interface that will make later changes very fiddly.

In the end though, I prefer a central interface over none.

@antonkomarev
Copy link
Contributor Author

@SpencerMalone Yeah, I thought about thin extra interface with only one method which doesn't extend any other interfaces, I think it should work as well.

@sagikazarmark
Copy link
Member

There's 100+ protobufs generating twirphp services.

Wow, that's nice to hear. :)

But, if there was a central ServerInterface, and after this PR we added a new function to it in a future release, you would be forced to regenerate all of those services in the moment that you updated twirphp

I believe that is precisely what the original library kept the shared part thin and that's the principle I've been trying to follow.

So if I'm hearing right what you both are saying: let's add some thin interface with a limited scope.

I can get behind that. Here is how I would address the breaking change concern:

Let's not add an overly generic ServerInterface then. Let's add an interface that addresses the specific use case that you both need/mentioned: simplifying service registration. That way we can basically say this interface is never ever going to change, because it serves this one single purpose: making registration easier.

If in the future the need for a new functionality emerges, we can add another interface. Yes, there is a risk that we may end up with dozens of interfaces, but:

A) we will cross that bridge when we get there, for now we are talking about one specific feature that needs an interface
B) it's generated code, so I'm less concerned about how nice the code is.

What do you think? Would this address the need you have for simpler server registration?

@sagikazarmark
Copy link
Member

(Also, I'm really-really bad at following GitHub notifications, so please keep nudging me if I don't answer for a long time)

@antonkomarev
Copy link
Contributor Author

@sagikazarmark I'm good with new thin interface.

@SpencerMalone
Copy link

SpencerMalone commented Nov 13, 2025

What do you think? Would this address the need you have for simpler server registration?

I am also good with this (and yes)!

@sagikazarmark
Copy link
Member

@antonkomarev would you be willing to rework this PR?

The current interface should be renamed something like ServerRegisterable or HasPrefix or something else (please help with the name).

ALso, it shouldn't extend the RequestHandler interface (and it should be added back to the generated code).

I guess that's it?

@antonkomarev
Copy link
Contributor Author

If we need very thin interfaces, I think that ServerRegisterable may lead to the desire to add new methods to it. Something like HasPrefix is much more accurate and looks more suitable for our case.

Or maybe I'd name it like:

  • ServerHasPathPrefix
  • ServerWithPathPrefix

@antonkomarev antonkomarev changed the title Simplify Server registration Simplify Server registration v1 Nov 16, 2025
@antonkomarev
Copy link
Contributor Author

antonkomarev commented Nov 16, 2025

@sagikazarmark Made new implementation in another PR:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants