Senior Architect, Palantir.net
Drupal 8 Web Services Lead
Drupal Representative, PHP-FIG
Advisor, Drupal Association
Loveable pedant
.1
Longest release cycle EVAR!
(Can we please never do that again?)
4 year cycle necessary for architecture overhaul
We don't need overhaul every version
Unknown length of cycle => uneven plans => longer cycle => uneven plans
The plan
Day 0: Drupal 8.0.0
0+1 month: Drupal 8.0.1 (bug fixes)
0+2 month: Drupal 8.0.2 (bug fixes)
…
0+6 month: Drupal 8.1.0 (new features!)
0+7 month: Drupal 8.1.1 (bug fixes)
0+12 month: Drupal 8.2.0 (new features!)
0+13 month: Drupal 8.2.1 (bug fixes)
…
Drupal LTS: 8.x.0 (last new features)
Drupal 9: just API-breaking changes
We don't break APIs in minor versions
(For some definition of API)
Druplicon: API?
An API is a function that performs an operation on a series of arrays.
—Druplicon
WRONG!
(This is why we can't have nice things.)
Old API definition
Any line that begins with "function"
(But not if the function starts with _
(except when that's included too because there's no other way to do something
(When your API definition looks like LISP, you have a problem)
)
)
And arrays. Lots of arrays.
We broke APIs between versions because our architecture didn't let us do otherwise
Now we can do otherwise
We won't break modules in minor versions
—Old BC pledge
We won't break well-behaved modules in minor versions
—New BC pledge
Well-behaved modules
Uses parts of the code we say are "safe"
Uses parts of the architecture we say are "safe"
Documentation problem!
Also, "separate safe from unsafe"
Use documentation to drive separation
Want to be able to change it? Define a safe layer in front of it.
Interfaces
@api: Safe to type hint, implement, and extend
@internal: Caveat implementor
Else: Safe to hint, implement, not extend
Classes
@api: Safe to type hint, __construct(), protected, public
@internal: Caveat implementor
Else: Safe to type hint, __construct(), public, not extend
Methods may be tagged separately
(We should probably treat traits the same)
We won't break @api and we'll warn you about breaks to normal classes/interfaces
—New BC pledge
There are no hard rules
Only guidelines
Strategic considerations
Security fixes trump everything
This will require case-by-case thought
Subsystem maintainers should be responsible
Private properties on @api classes?
@internal: Reserve the right to change, not willy nilly
Simple pattern application will be insufficient; no rules to shield us from being intelligent
Subsystem maintainers usually have final say!
General rules
Database schema is always @internal
Tests are always @internal
Controllers are always @internal
YAML file structure MAY be an API
Test base classes are like any other class
Tactical guidelines
"Tagged services": @api interface, tag
"Tagged service aggregator": @internal class
Extend classes (*Base): @api with privates
Break base classes into multiple traits
Plugins: Mostly @api interface; Manager is @internal
Traits > Base classes: Some @api, some @internal
Think through how we want devs to interact (not how they might interact)
Small surface area is better than large surface area
Container guidelines
Mark services private (no $container->get())
Public services must have an interface
Public service names are @api
Declarative guidelines
Service tag names: API
Routing key names: API
Code used by routing key (_form, _entity_view): @internal
Config keys: API (unless wrapping service)
Often the API is behavior, not code. So declare the behavior the API, not the code.
Other guidelines
3rd party interfaces: !@api => consider empty extension
Base class/interface mismatch: Code smell (needs traits?)
Form arrays: Mostly @internal Except node form?
Render arrays: Key defs are API, not specific usage
Base classes example: BlockBase
Forms: Sneeze and they "break". Need to allow feature addition
Example: Breadcrumbs
BreadcrumbBuilderInterface: @api
"breadcrumb_builder" DI tag: @api
"breadcrumb" service name: @api
ChainBreadcrumbBuilderInterface: normal
BreadcrumbManager: @internal
BreadcrumbBuilderBase: Remove for traits
BreadcrumbManager: implementation detail
BreadcrumbBuilderBase = 2 traits. Not needed
@api
Interfaces
RouteFilterInterface (wrapped)
RouteEnhancerInterface (wrapped)
HtmlFragmentInterface
Classes
RouteSubscriberBase
ControllerBase
HtmlFragment
All exceptions
normal
Interfaces
UrlGeneratorInterface
RouteBuilderInterface
RouteProviderInterface
ControllerResolverInterface
Classes
LazyLoadingRouteCollection
RouteBuildEvent
UrlGenerator
@internal
Classes
MatcherDumper et al
UrlMatcher
RouteProvider
CompiledRoute
Route filters
Route enhancers
RouteBuilder
RouteCompiler
RoutePreloader
It's the YAML, stupid!
node.content_overview:
path: '/admin/content'
defaults:
_title: 'Content'
_entity_list: 'node'
requirements:
_permission: 'access content overview'
_title / _title_callback
_content / _controller
_form
_format / method
_entity_view / _entity_form / _entity_list
Changing what we didn't expect
Rename classes/interfaces
class BookManager {
public function getAllBooks() {}
}
class BookRepository extends BookManager {
public function doSomethingNew() {}
}
function core_cool_stuff(BookRepository $repository) {
$books = $repository->getAllBooks();
$repository->doSomethingNew();
}
function contrib_cool_stuff(BookManager $manager) {
$books = $manager->getAllBooks();
}
Future-looking interfaces
/**
* @api
*/
interface OldInterface {
public function foo();
}
interface NewStyleInterface extends OldStyleInterface {
public function bar();
}
function cool_stuff(OldStyleInterface $service) {
if ($service instanceof NewStyleInterface) {
$bar = $service->bar();
}
else {
$bar = "Some previously assumed value.";
}
// ...
}
When we do that, file a D9 issue to clean it up again
Warning
We are going to screw up and want to change something we didn't plan for
Learn by failure
We can raise guarantee, never lower.
Be conservative with @api for now; expand in 8.1, 8.2
We can always raise guarantee, never lower. (So be conservative to start.)
Expectation: We're going to screw up and want to change something we didn't setup for that
Learning experience! We know not to screw it up in D9.
Deadline: 8.0.0.