在保持数据完整性的同时对循环依赖进行建模
Posted
技术标签:
【中文标题】在保持数据完整性的同时对循环依赖进行建模【英文标题】:Modeling circular dependencies while maintaining data integrity 【发布时间】:2021-10-11 14:22:57 【问题描述】:我正在设计一个音乐信息系统。我有几个相互连接的实体。 以下是部分域代码。
class Album
private Set<Track> tracks;
private boolean published;
public Set<Track> getTracks()
return this.tracks;
public boolean isPublished()
return this.published;
public void publish()
System.out.println("Album.publish() called");
this.published = true;
this.tracks.forEach(track -> track.publish());
class Track
private boolean published;
private Album album;
public boolean isPublished()
return this.published;
public Album getAlbum()
return this.album;
public void publish()
// if track is single (this.album == null), set published to true
// if track is part of an album and the album is NOT published, return;
// if track is part of an album and the album is published, set published to true
if(this.album != null && !this.album.isPublished())
return;
this.published = true;
Track 是一个独立的实体。它可以是单首曲目(即没有专辑)。所以实际上需要album
属性。
一个域规则是,当专辑存档(即未发布)时,其曲目也不能发布,如果专辑已发布,则其任何曲目都可以发布或存档。 问题是当专辑发布时(例如album1.publish()
),其曲目的publish()
方法也会被调用。但是track1.publish()
根据已有的副本(未发布)检查专辑是否已发布。我该如何解决这个问题?
【问题讨论】:
如果Album
是一个实体,则不应有“副本”——实体具有唯一标识,所有引用都应指向同一个对象。
正确。我应该如何在 Java 中实现它?据我所知,Java 总是按值传递。 @埃里克森
***.com/questions/40480/… 很好地了解 pass-by-value 与 pass-by-ref
是的,将对父 Album
的引用传递给 Track
(按值,因为所有引用都在 Java 中传递)。强制数据完整性的一种方法是删除直接为Track
设置Album
的任何API,而是仅在Album
上提供addTrack(Track)
方法,该方法更新轨道集合并设置Album
通过包私有访问。
【参考方案1】:
如果您按行为拆分域模型实体,则可以摆脱所描述的限制
让我们为这些实体创建一些接口:
interface AlbumId
String asString();
AlbumId Absent = () -> "NO ALBUM AT ALL";
interface Publication
void publish() throws Exception;
void archive() throws Exception;
boolean published();
interface Track
TrackId id();
AlbumId albumId(); //smart type (as DDD suggest), therefore, no more nulls
现在您可以通过创建类来执行规则,该类将为您提供可以发布的曲目列表:
public class TracksReadyToPublishOf implements Supplier<Map<TrackId, TrackPublication>>
//this class may access to cache and have dosens of other optimizations
public TracksReadyToPublishOf(AlbumId id)...
@Override public get()...
然后您可以在任何地方重复使用您的代码来检查您的规则:
public class TrackPublication implements Publication
private final Track track;
private final Supplier<Map<TrackId, TrackPublication>> allowedTracks;
//easy for unit testing
public SmartTrackPublication(Track track, Supplier<Map<TrackId, TrackPublication>> allowedTracks)
this.track = track;
this.allowedTracks = allowedTracks;
public SmartTrackPublication(Track track)
this(track, new TracksReadyToPublishOf(track.albumId());
@Override
public publish() throws AlbumArchivedException
if(this.albumId != AlbumId.Absent)
if(!this.allowedTracks.get().containsKey(this.track.id()))
throw new AlbumArchivedException();
this.allowedTracks.get().get(this.id()).publish();
对于专辑发布:
public class AlbumPublication implements Publication
private final AlbumId id;
private final Producer<Map<TrackId, TrackPublication>> tracks
private AlbumWithTracks(AlbumId id, Producer<Map<TrackId, TrackPublication>> tracks)
this.id = id;
this.tracks = tracks;
public AlbumWithTracks(AlbumId id)
this(id, new TracksReadyToPublishOf(id))
...
@Override publish() throws Exception
//code for publishing album
for(TrackPublication t : Arrays.asList(
this.tracks.get()
))
t.publish(); //track can publish anyway if it presents in list above
【讨论】:
以上是关于在保持数据完整性的同时对循环依赖进行建模的主要内容,如果未能解决你的问题,请参考以下文章
如何在保持表关系完整性的同时从 MS Access 数据输入表单添加数据