0

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.

2 Answers 2

1
+50

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
  • I see. I was thinking like that too. But CMSampleBuffer is not an entity since it came from the View. So the Use Case shouldn't know about its existence, right? Oct 4, 2022 at 12:39
  • 1
    Yes, if we completly embrace the Clean Architecture style, then we would use our own data model in the business logic and map to a different model in the outer regions (the controllers). Or reuse the model of the logic in the outer regions. As always it depends on the context which approach to use. If we use own models in each region, have a lot of mapping and then nothing changes for the rest of the application life, then it was the wrong decision. If there are some changes, then this mapping decision may save our app :-)
    – JanRecker
    Oct 5, 2022 at 7:09
  • I see. That's a good point. Thanks for the answer. Oct 8, 2022 at 3:45
1

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?

enter image description here =

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:

enter image description here

This thing doesn't have a "Data Layer". If you want rules for a "Data Layer" look at some other architecture.

2
  • This thing doesn't have a "Data Layer". If you want rules for a "Data Layer" look at some other architecture. I know that Clean Architecture doesn't have Presentation-Domain-Data layering per-se but some people (whose ideas I happen to follow) usually described this: MVx = Presentation layer; UseCases/Entities = Domain layer; Repositories/Gateways/Interface-Adapters/Controllers/etc = Data layer. Like in these articles: fernandocejas.com/blog/engineering/… , proandroiddev.com/… Sep 27, 2022 at 5:37
  • BTW, the question still stands. Let me rephrase: 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)-> send it to the UseCase (eg. CheckForObjectDetectionUseCase.check(with dto: SampleBufferDTO) -> Observable<DetectedObjectEntity>) -> the UseCase then use the Interface-Adapter with the DTO (AppleMachineLearningAdapter.detectObject(_ dto: SampleBufferDTO) -> Observable<DetectedObjectEntity>) in which it will map the DTO back to CMSampleBuffer first before being used? Sep 27, 2022 at 5:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.