So I’ve got a simple object recognition app in iOS. I am trying to create an Interface-Adapter for the machine learning / computer vision like this:
protocol MachineLearningInterface {
func detectObject(from sampleBuffer: CMSampleBuffer) -> Observable<DetectedObjectEntity>
}
// Either Apple or Google
class AppleMachineLearningAdapter {
func detectObject(from sampleBuffer: CMSampleBuffer) -> Observable<DetectedObjectEntity> {
...
}
}
class GoogleMachineLearningAdapter {
func detectObject(from sampleBuffer: CMSampleBuffer) -> Observable<DetectedObjectEntity> {
...
}
}
In the ViewController
the input is from this delegate function:
extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate {
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
viewModel.outputSampleBuffer.accept(sampleBuffer) //viewModel.outputSampleBuffer is a BehaviorRelay
}
}
Now, I am confused about how to make the UseCase here because obviously the Presentation Layer and the Data Layer is coupled because they need the CMSampleBuffer
to function. Is it okay to do this? Because in that case, I also need to use CMSampleBuffer
in the UseCase and Entity. But UseCases and Entities shouldn’t know about the layers above them, right?
Thanks.
PS: Maybe I’ll rephrase the question: What if my View/ViewModel use the same object as the Interface-Adapter/Controller (ie.the CMSampleBuffer
)? Do I have to map CMSampleBuffer
to DTO (say, SampleBufferDTO
), then send it to the UseCase (eg. CheckForObjectDetectionUseCase.check(with dto: SampleBufferDTO) -> Observable<DetectedObjectEntity>
), then use the Interface-Adapter with the DTO (AppleMachineLearningAdapter.detectObject(_ dto: SampleBufferDTO) -> Observable<DetectedObjectEntity>
) in which it will map the DTO back to CMSampleBuffer
.
Or just do the whole logic for object detection in the View/ViewModel without having a UseCase in-between?
BTW, I also got the same problem when trying to bind the View/ViewModel to the other layers for a video call feature where the video call services such as Twilio, Agora, etc should’ve been accessed behind a Controller but they are pretty much coupled with the AVFoundation and CallKit from the View.
Most architecture styles try to achieve one “simple” thing: “The things that are volatile should depend on the stable stuff, not the other way around”.
The CMSampleBuffer
object is data + logic.
Using a DTO for talking between layers, means that the data is kind of stable and will not change in the different usages (presentation or controller), while the logic may be different and/or will likely change differently in the different layers.
If that is the case, then it makes sense to use a DTO between layers.
But, if the logic is also kind of stable, or will always change in both layers identicaly, then there is no advantage using a DTO.
Architecture is always about understanding the business and looking into the crystal ball how the business may change.
We CAN try to support all possible changes (like having own entities per layer, and use mapping logic between layers), but that comes with a cost.
For example flexibility is normaly bad for performance.
It is not possible to get everything. Therefore we have to get a feeling about the stable stuff and the changing stuff and then design our architecture accordingly.
So about your case:
Working with DTOs and having your own entity per layer is more effort, the code is less understandable, harder to debug and less performant. At the same time CMSampleBuffer
seems to be quite stable.
=> I would use the the entity through all layers.
3
Now, I am confused about how to make the UseCase here because obviously the Presentation Layer and the Data Layer is coupled because they need the CMSampleBuffer to function.
Well sure they both need data but who said it has to be the same data?
=
Here you see the use case interactor transforming input data into output data. The arrows show what knows about what. The presenter doesn’t know the input data exists. The controller doesn’t know the output data exists. And naturally the data doesn’t know anything exists.
If you’re thinking that doesn’t sound right. This whole clean architecture thing was supposed to be a set of rules about what layers could know about what layers. Well sure, but let me point out a little flaw:
This thing doesn’t have a “Data Layer”. If you want rules for a “Data Layer” look at some other architecture.
2