Imagine there is an Order aggregate with two entities: Order and OrderLine. The Order entity is the Aggregate root. Generally, your application will add, remove and edit OrderLines from order during its lifecycle. Want to put this knowledge into practice? Download our Quick Start Package here.
According to DDD (and general best practices), entities other than the aggregate root should not be directly referenced outside the Aggregate. That means that the OrderLine identifier should always be used in the context of an Order. In practical terms, that means that you should always pass both the OrderId and the OrderLineId if you want to do something with an OrderLine. The OrderId is used to locate the Aggregate (Order), which is then asked to perform whatever operation on and OrderLine, identified with the OrderLineId.
There are roughly three options for choosing identifiers: random, hash, or sequential. The advantage of random and hash identifiers is that the client can generate the identifier and reference an instance without waiting for the result of the creation command.
Never use identifiers generated by the query database to identify instances of entities. Since these identifiers are not part of the command model, you will end up with events that you cannot replay. Whatever you do, make sure the Aggregate knows about the identifiers used.
Suppose you found yourself ending up with a list of entities without their own identifier (for example, because you never anticipated a requirement to edit exiting items). In that case, you will need to generate identifiers for the past events. You're probably best off with a hash-based identifier for those entities. First, calculate the hash based on immutable information in the entity. Then, when you add the identifier to the event, you can create an upcaster that builds the ID based on information available inside the event.