You're naturally working your way towards entity-component systems.
The final step which is the hardest to take if you've been applying an OOP mindset for years, is to turn those shared components into raw data, but it is only then that you can really come to appreciate the true flexibility of the ECS. I actually stubbornly resisted doing that until a second iteration where I just made components into raw data.
The way to kind of counteract the feeling that this seems all wrong and a total violation of encapsulation and information hiding is to remember that typically only one or two systems will be accessing any given component and often just one system that actually transforms (mutates) that data. As a result it's actually not much harder, if any, to maintain intracomponent invariants. It also tends to make it easy to maintain broader, intercomponent invariants with coarse systems processing everything.
Entities are just containers of components (with one component type instance maximum per entity). Components are just raw data. Systems process entities/components and are the sole providers of functionality between these three. Systems are also generally decoupled from each other and typically do not call functions into each other. Most systems also resemble the loopy pattern of:
for each entity with these specific components:
do something with the components
For instance, the render system above might be like this:
for each entity with pos/velocity and sprite components:
render sprite component at pos
While the movement system might look like this:
for each entity with pos/velocity components:
pos += velocity * time_elapsed
