The road to 8.1 – Druplicon: API? – Documentation kick-start



The road to 8.1 – Druplicon: API? – Documentation kick-start

0 0


slides-road-to-81


On Github Crell / slides-road-to-81

The road to 8.1

Presented by Larry Garfield (@Crell)

@Crell

  • 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

OK, let's try something else

A new release cycle

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 ourarchitecture 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.

Documentation kick-start

Symfony BC policy

Moar @ tags!

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

So what is an @api?

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

Example:

Routing

@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

So where's the API?

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 andwant 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

Discuss

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.

Larry Garfield

Senior Architect, Palantir.net

Making the Web a Better Place

Keep tabs on our work at @Palantir

Want to hear about what we're doing?