Wow, the poison of "the most important thing about an object is what attributes it has" mindset (that is pushed by tools like Moose) really has ruined many people's concept of OO.
If all the attributes are "ro", then how is the resulting object not immutable (interface-wise at least)?
OO is an abstraction. The point of abstraction in programming is to hide implementation details by providing an interface. One of the important ways to hide details is called "data hiding" -- you should not just plumb through your interfaces full access to the data used in the implementation. The point of interfaces is to be as narrow as possible to reduce the complexity of the remaining part of the problem after you have factored out the implementation details you have isolated (inside of the class, in this case).
Not having mutators dedicated to every single attribute doesn't make your object immutable from an interface perspective. It simply forces your interface to accomplish changes to attributes only via methods that have a purpose that requires, in the implementation details, that attribute values be modified.
That's not an immutable object. It is merely an object that has a chance of having a decent interface designed. It indicates a class that might adhere to good programming practices recognized in the 1970s and still rightly praised today.
OO is not a replacement for modular programming. It is a somewhat newer tool that can be used to facilitate modular programming, but only if you use the tool appropriately.
I recently got to see a new form of the problems with "objects as an 'interface' to bags of attributes". It has been a while now since I realized that such designs lead to poor abstraction that leads to hard to maintain code after a project has gone through enough maintenance. Just recently I got to see the results of trying to improve code by factoring out part of the complexity into a new class, but then making that class mostly just a bag of attributes.
The immediate effect was to make the code more complicated to understand. Sure, a bit of structure had been imposed. And a few tiny bits of actual behavior did get put into just a couple of methods in the new class. The overly complex code even got slightly smaller.
But the result was harder to understand because most of the uses of data that was supposed to be hiding in the new objects was being manipulated by code outside of the class. So you could not make much sense of the class by just looking at the class. All this meant was that, when reading parts of the overly complex code, now you would find all of the places where the 'foo' datum was used and not be able to make sense of 'foo'. Then you'd realize that there must be some more manipulations involving 'foo' somewhere else (or, worse, just come to inaccurate conclusions).
It just made you have to jump back and forth between two files of code whenever you tried to understand anything that involved data now held (quite loosely) in the new object.
The worst offense on this front was a method in the new class that wrote an output file. This method had to be fed the name of the file to write out. But that name was built from the data held in the object. The code using the new class actually queries the object (via its many accessors) to get the parts needed to construct the name of the output file, does the mundane but non-trivial steps to combine those, and then feeds the result back into the object.
No, the object isn't "immutable interface-wise", it just isn't obviously badly designed, interface-wise.