There are a number of different methods for extending the standard schema to include application specific attributes. The best method to use depends on the use case.
(1) The most common method is to create a new auxiliary object class (type = auxiliary) containing the custom attributes. When the auxiliary object class is added to an entry's object class list, the custom attributes become available. The reason for using an auxiliary object class is that they are not structural, that is, don't occupy a node in the hierarchy and don't require a naming attribute.
(2) Just to check terminology, when you say extending the schema, do you mean storing your custom attributes as a subordinates to the base entry? For example, if we have entry "uid=1234,ou=users,c=au" including custom attributes as a subordinate object say "cn=appData,uid=1234,ou=users,c=au". This method is often used when the relationship between the base entry and custom data is one-to-many or if there are a number of applications and separation is required. (3) Retrieving such data requires two searches (retrieve object and performing a one-level scoped search on the retrieved object). We do have a feature called "views" to return both objects in one hit but that is a different topic entirely.
Extension of (2), We have some applications that store custom information in a separate part of the hierarchy and link to the main object via an attribute of distinguishedName syntax. This is typically performed when the underlying schema cannot be modified, for example, creating a session store for an existing deployment. It is then up to the application to maintain the separate store and take care of referential integrity (maintaining the link between the base entry and the custom entry).