My general recommendation is to keep both the source components of your transformation for ease of manipulation:
- local translation vector
- local orientation quaternion
- local axis-aligned scale triplet, or uniform scale scalar
And the resulting object-to-world matrix (factoring in the transformations of the parent hierarchy too).
Keeping these separate gives you the best of both worlds:
Scripts that need to modify just a rotation or just a scale can act on those properties directly, without decoding and re-encoding them into the matrix on every use.
Your rendering (and perhaps physics) systems have access to pre-concatenated object-to-world matrices exactly as they need, and don't need to take responsibility for building them on the fly.
You can even keep these two views of the transformation in separate components/buffers for cache utilization, if your code often works with just one or the other at a time.
You can use a dirty flag to avoid redundant re-computation of the complete matrix for objects that stay stationary for the frame. (Or a dirty list, to avoid iterating your whole collection with random-ish branching along the way) This also saves you re-computing the whole matrix for each intermediate step when an object's local properties are modified by several effects in a single frame - instead you "bake in" the combined effects lazily, the next time the matrix is needed.
A major benefit of separating out the source values is controlling the accumulation of errors. By storing these components separately, they remain pristine - repeated rotation won't "leak" rounding errors into deviations of your scale parameter for example - and you can more easily maintain invariants like orthogonality (something you get by construction when storing the rotation as a quaternion rather than a matrix).
Your hierarchy can be expressed as simply as a parent id / index - keeping the transforms sorted by this index can let you update a whole hierarchy in a single linear pass.