Giter Site home page Giter Site logo

Comments (8)

pmjones avatar pmjones commented on August 23, 2024 3

@djmattyg007 @Federkun @kevinsmith --

I suppose this is as good a time as any to reveal Transit, a persistence-to-domain system for Atlas that I've been developing off-and-on for several weeks now:

https://github.com/atlasphp/Atlas.Transit

It is experimental, unfinished, and unsuitable for production. I can guarantee breaking changes, thus the 0.x version. Of note, it lacks Value Object support.

Even though it is not done, it may give you ideas of your own. And of course if you have PRs, send them along.

from atlas.orm.

Federkun avatar Federkun commented on August 23, 2024 1

I was wondering the same thing @djmattyg007. I came out with this:

// PM
class RecordDiscussion
{
    public $id;
    public $authorName;
    public $close;
}

// DM
class Discussion
{
    /** @var DiscussionId */
    private $id;
    /** @var Author */
    private $author;
    /** @var bool */
    private $isClosed = false;
    
    // Make sure that you can create new Discussions only using named constructors
    private function __construct() {}
    
    public static function createFrom(DiscussionId $id, Author $author): self
    {
        $discussion = new self();
        $this->id = $id;
        $this->author = $author;
        
        return $discussion;
    }

    public function close(): void
    {
        if ($this->isClosed) {
            throw new Exception('This discussion is already closed..');
        }
        
        $this->isClosed = true;
    }
    
    public function open(): void
    {
        if (!$this->isClosed) {
            throw new Exception('This discussion is already open..');
        }
        
        $this->isClosed = false;
    }
    
    // No getters here. I'm moving towards cqrs.
    
    protected static function reconstituteFromDiscussionRecord(RecordDiscussion $record): self
    {
        $discussion = new self();
        $discussion->id = DiscussionId::fromString($record->id);
        $discussion->author = Author::fromName($record->authorName);
        $discussion->isClosed = (bool) $record->close;
        
        return $discussion;
    }
}

The question is: "how can we map the DM and PM?" What do you suggest? Something like this?

class DiscussionTranslator // I can't think of a better name right now
{
    public function fromPersistenceModelToDomainModel(RecordDiscussion $record): Discussion
    {
        $class = new ReflectionClass(Discussion::class);
        $method = $class->getMethod('reconstituteFromDiscussionRecord');
        $method->setAccessible(true);
        
        return $method->invokeArgs(null, [$record]);
    }
    
    public function fromDomainModelToPersistenceModel(Discussion $discussion): RecordDiscussion
    {
        $record = new RecordDiscussion();
        $record->id = $this->getProperty($discussion, 'id')->toString();
        $record->authorName = $this->getProperty($discussion, 'author')->toString();
        $record->close = $this->getProperty($discussion, 'isClosed');

        return $record;
    }
    
    // ugly hack
    private function getProperty($obj, $prop)
    {
        $reflection = new ReflectionClass($obj);
        $property = $reflection->getProperty($prop);
        $property->setAccessible(true);
        return $property->getValue($obj);
    }
}

If I don't want apply event sourcing, I'm constrained with this?

Finally:

class DiscussionRepository
{
    private $mapper;
    private $translator;
    
    public function __construct(DiscussionMapper $mapper, DiscussionTranslator $translator)
    {
        $this->mapper = $mapper;
        $this->translator = $translator;
    }

    public function save(Discussion $discussion)
    {
        $record = $this->translator->fromDomainModelToPersistenceModel($discussion);

        // $this->mapper->update? $this->mapper->insert?
    }
    
    public function fetch(DiscussionId $id)
    {
        $record = $this->mapper->select(['id' => $id->toString()])
            ->with(['authorName', 'close']);
     
        return $this->translator->fromPersistenceModelToDomainModel($record);
    }
}

Full example: https://3v4l.org/oL5OO

from atlas.orm.

pmjones avatar pmjones commented on August 23, 2024

how do you go about persisting changes to the domain model back to the storage layer
...
This class doesn't expose all of its properties through getter methods, to avoid it being abused somewhere else in the application.

My general presumption is that getter methods, not being able to change the state of the domain object, is abuse-resistant. The furthest extent I can imagine is that someone might query the state of the object and then use that to decide on a course of action (the opposite of "tell don't ask"). Personally, that's not been a problem I've experienced; being able to "get" the various entities that make up an aggregate, for example, has not seemed at odds with DDD -- but then I don't claim to be a DDD expert so I might have been breaking some rules.

Perhaps providing code examples might help me (and others) understand the problem better ... ?

from atlas.orm.

Federkun avatar Federkun commented on August 23, 2024

This is how I have chosen to do it: https://gist.github.com/Federkun/a8e007fc7d9445f100981fb5ccc57407
It's not without of problems, but it's good enough.

from atlas.orm.

kevinsmith avatar kevinsmith commented on August 23, 2024

I'm running into this same issue myself. Not an Atlas-specific thing per se, just not often seen in many of the other popular ORMs because so many of them couple the persistence layer to the domain layer.

If we've followed the guidance to fully separate persistence from domain (https://github.com/atlasphp/Atlas.Orm/blob/1.x/docs/domain.md#map-from-persistence-to-domain), how do we then take the data in our POPO and get Atlas to update the appropriate row in the DB?

The only solution I've been able to come up with is essentially keeping a cache of previously retrieved Atlas Record instances in an array on a class property of your repository. Then when you inject the POPO (the Thread instance in the example) into your repository's save() method, the repository matches it up with the cached Atlas Record instance (by primary key), updates the values on the Record accordingly, and passes the Record object into $atlas->update.

It feels like there's got to be a better way and I just can't see it.

from atlas.orm.

djmattyg007 avatar djmattyg007 commented on August 23, 2024

Christmas has come early :)

from atlas.orm.

pmjones avatar pmjones commented on August 23, 2024

With Atlas.Transit as at least an example of how to do persist changes back to the storage layer, I am closing this issue. Please comment here if you find reason to re-open it. Thanks all!

from atlas.orm.

kevinsmith avatar kevinsmith commented on August 23, 2024

The only solution I've been able to come up with is essentially keeping a cache of previously retrieved Atlas Record instances in an array on a class property of your repository. Then when you inject the POPO (the Thread instance in the example) into your repository's save() method, the repository matches it up with the cached Atlas Record instance (by primary key), updates the values on the Record accordingly, and passes the Record object into $atlas->update.

Just coming back to this, and after walking through the code with xdebug, it appears that there was no need for me to do this as Atlas already does this through IdentityMap. Just fetch the record through Atlas as normal and if it was retrieved in the same request, it won't hit the DB again.

Is that right, @pmjones?

from atlas.orm.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.