i think that mistakes were made but the core principles are corrrect.
i am working now on buliding relays as multiple components, so, the spec has many things, and some naturally cluster functionally, and other things are able to be separated.
i'm currently rewriting realy to be a bare naked unauth simple relay, and then i'm going to build a relay auth proxy, it will be set up between the client and the relay to control access, thus simplifying the build of both parts
the same thing could then be done at a third layer with network transport, giving you a p2p type networking
i can think of other layers to add too, like proxy caches, that are dumb and can only comprehend "do you have event ID X" - sorta like blossom but basically nostr query only for event IDs, or for pubkeys, or for the newest thing.
actually, i started building something inspired by chatter with semisol, an HTTP API call that lets you just poll a relay for its internal reference numbers (monotonic sequence numbers of each event) and a peer then only has to keep a latest sequence number state value related to another relay and the relay can then deliver all the newer events to it no muss no fuss.
i do think the nostr protocol has a load of ugly bits in it but so long as everyone mainly respects the nip-01 rule about string escaping i'm good. all the other things are copable.